From 3e017e7eb884f45a4fe2aac4a7c340a6a26d7966 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Fri, 20 Aug 2021 14:36:37 +0200 Subject: [PATCH] chores: upgrade v042.7 -> v0.42.9 (#108) * docs: add v0.43 release version option (backport #9506) (#9677) * docs: add v0.43 version option (#9506) Co-authored-by: ryanchrypto <12519942+ryanchrypto@users.noreply.github.com> (cherry picked from commit 325dabd83c66c23a1f2320783de552a14090a086) # Conflicts: # docs/versions * fix conflicts Co-authored-by: Ryan Christoffersen <12519942+ryanchristo@users.noreply.github.com> Co-authored-by: Amaury M <1293565+amaurym@users.noreply.github.com> * chore: Add missing entry in 0.42.7 Changelog (#9722) * fix: support output flag on tx commands (backport #9717) (#9772) * fix: support output flag on tx commands (#9717) ## Description Closes: #9684 * feat: Query txs by signature and by address+seq (backport #9750) (#9783) * feat: Query txs by signature and by address+seq (#9750) ## Description Closes: #9741 * fix: hardcoded ledger algo on `keys add` (backport #9766) (#9804) * fix: hardcoded ledger algo on `keys add` (#9766) ## Description Closes: #9734 cc: @jleni * chore: Add Changelog and Release Notes for v0.42.8 (#9807) * chore: Add Changelog and Release Notes for v0.42.8 * Change date * feat: Improve withdraw-all-rewards UX (backport #9781) (#9825) * feat: Improve withdraw-all-rewards UX (#9781) ## Description Related to #9489, this PR improves the UX when using the `withdraw-all-rewards` command by forcing the broadcast mode to `block` if the chunk size is greater than 0. This will ensure that the transactions do not fail even if the user uses invalid broadcast modes for this command (`sync` and `async`). * fix: Capability Issue on Restart, Backport to v0.42 (#9835) * implement BeginBlock fix * add changelog * fix lint * address reviews * Update CHANGELOG.md Co-authored-by: Aleksandr Bezobchuk * address reviews * Apply suggestions from code review Co-authored-by: Robert Zaremba * move store check * add api breaking changelog * fix lint Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Robert Zaremba * Fixed the --recover flag not working properly inside the init command (#9201) (#9850) Co-authored-by: Alessio Treglia (cherry picked from commit fdbc32e50c93689ec7352c11edb6d93f33d47a6c) Co-authored-by: Riccardo Montagnin * chore: prepare 0.42.9 release (#9852) * prepare 0.42.9 release * add 9201 to changelog Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Ryan Christoffersen <12519942+ryanchristo@users.noreply.github.com> Co-authored-by: Amaury M <1293565+amaurym@users.noreply.github.com> Co-authored-by: Aditya Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Robert Zaremba Co-authored-by: Riccardo Montagnin --- CHANGELOG.md | 30 ++++ RELEASE_NOTES.md | 14 +- client/flags/flags.go | 1 + client/keys/add.go | 2 +- crypto/keyring/keyring.go | 7 +- crypto/keyring/keyring_ledger_test.go | 4 +- crypto/ledger/ledger_secp256k1.go | 6 +- docs/versions | 2 + simapp/app.go | 4 +- types/events.go | 5 + x/auth/ante/sigverify.go | 68 ++++++++ x/auth/client/cli/cli_test.go | 181 ++++++++++++++++++--- x/auth/client/cli/query.go | 111 +++++++++++-- x/auth/client/cli/query_test.go | 32 ++++ x/capability/capability_test.go | 97 +++++++++++ x/capability/genesis.go | 5 +- x/capability/genesis_test.go | 59 +++++++ x/capability/keeper/keeper.go | 74 ++++----- x/capability/keeper/keeper_test.go | 3 + x/capability/module.go | 14 +- x/capability/types/keys.go | 3 + x/distribution/client/cli/tx.go | 12 +- x/genutil/client/cli/init.go | 3 +- x/ibc/core/02-client/keeper/client_test.go | 4 +- 24 files changed, 640 insertions(+), 101 deletions(-) create mode 100644 x/auth/client/cli/query_test.go create mode 100644 x/capability/capability_test.go create mode 100644 x/capability/genesis_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 90bebf64c6..ebd2da4a00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,35 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## Unreleased +## [v0.42.9](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.42.9) - 2021-08-04 + +### Bug Fixes + +* [\#9835](https://github.com/cosmos/cosmos-sdk/pull/9835) Moved capability initialization logic to BeginBlocker to fix nondeterminsim issue mentioned in [\#9800](https://github.com/cosmos/cosmos-sdk/issues/9800). Applications must now include the capability module in their BeginBlocker order before any module that uses capabilities gets run. +* [\#9201](https://github.com/cosmos/cosmos-sdk/pull/9201) Fixed ` init --recover` flag. + + +### API Breaking Changes + +* [\#9835](https://github.com/cosmos/cosmos-sdk/pull/9835) The `InitializeAndSeal` API has not changed, however it no longer initializes the in-memory state. `InitMemStore` has been introduced to serve this function, which will be called either in `InitChain` or `BeginBlock` (whichever is first after app start). Nodes may run this version on a network running 0.42.x, however, they must update their app.go files to include the capability module in their begin blockers. + +### Client Breaking Changes + +* [\#9781](https://github.com/cosmos/cosmos-sdk/pull/9781) Improve`withdraw-all-rewards` UX when broadcast mode `async` or `async` is used. + +## [v0.42.8](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.42.8) - 2021-07-30 + +### Features + +* [\#9750](https://github.com/cosmos/cosmos-sdk/pull/9750) Emit events for tx signature and sequence, so clients can now query txs by signature (`tx.signature=''`) or by address and sequence combo (`tx.acc_seq='/'`). + +### Improvements + +* (cli) [\#9717](https://github.com/cosmos/cosmos-sdk/pull/9717) Added CLI flag `--output json/text` to `tx` cli commands. + +### Bug Fixes + +* [\#9766](https://github.com/cosmos/cosmos-sdk/pull/9766) Fix hardcoded ledger signing algorithm on `keys add` command. ## [v0.42.7](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.42.7) - 2021-07-09 @@ -50,6 +79,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#9645](https://github.com/cosmos/cosmos-sdk/pull/9645) Use correct Prometheus format for metric labels. * [\#9299](https://github.com/cosmos/cosmos-sdk/pull/9299) Fix `[appd] keys parse cosmos1...` freezing. * (keyring) [\#9563](https://github.com/cosmos/cosmos-sdk/pull/9563) fix keyring kwallet backend when using with empty wallet. +* (x/capability) [\#9392](https://github.com/cosmos/cosmos-sdk/pull/9392) initialization fix, which fixes the consensus error when using statesync. ## [v0.42.6](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.42.6) - 2021-06-18 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 78536d4d7b..35ff8ae10d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,10 +1,10 @@ -# Cosmos SDK v0.42.7 "Stargate" Release Notes +# Cosmos SDK v0.42.9 "Stargate" Release Notes -This release includes various minor bugfixes and improvments, including: +This release includes an important `x/capabiliy` module bug fix for 0.42.7 and 0.42.8 which prohibits IBC to create new channels (issuse [\#9800](https://github.com/cosmos/cosmos-sdk/issues/9800)). +The fix changes the `x/capability/keeper/Keeper.InitializeAndSeal` method behavior and requires to update an app module manager by adding x/capability module to Begin Blockers. -- a x/capability initialization fix, which fixes the consensus error when using statesync, -- CLI improvements such as fixing the `{appd} keys parse` subcommand and a better user error when `--chain-id` is not passed in the `{appd} tx multisign` subcommand, -- add a new `Trace()` method on BaseApp to return the `trace` value for logging error stack traces, -- IBC fixes for the `Transfer` event and telemetry metrics. +We also fixed ` init --recovery` mode where the mnemonic was not handled correctly. -See the [Cosmos SDK v0.42.7 milestone](https://github.com/cosmos/cosmos-sdk/milestone/48?closed=1) on our issue tracker for the exhaustive list of all changes. +We also changed ` tx distribution withdraw-all-rewards` CLI by forcing the broadcast mode if a chunk size is greater than 0. This will ensure that the transactions do not fail even if the user uses invalid broadcast modes for this command (sync and async). This was requested by the community and we consider it as fixing the `withdraw-all-rewards` behavior. + +See the [Cosmos SDK v0.42.9 milestone](https://github.com/cosmos/cosmos-sdk/milestone/53?closed=1) on our issue tracker for the exhaustive list of all changes. diff --git a/client/flags/flags.go b/client/flags/flags.go index b305412d46..7d4de556b6 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -94,6 +94,7 @@ func AddQueryFlagsToCmd(cmd *cobra.Command) { // AddTxFlagsToCmd adds common flags to a module tx command. func AddTxFlagsToCmd(cmd *cobra.Command) { + cmd.Flags().StringP(tmcli.OutputFlag, "o", "json", "Output format (text|json)") cmd.Flags().String(FlagKeyringDir, "", "The client Keyring directory; if omitted, the default 'home' directory will be used") cmd.Flags().String(FlagFrom, "", "Name or address of private key with which to sign") cmd.Flags().Uint64P(FlagAccountNumber, "a", 0, "The account number of the signing account (offline mode only)") diff --git a/client/keys/add.go b/client/keys/add.go index 8ae27f0a75..1e87835500 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -200,8 +200,8 @@ func RunAddCmd(ctx client.Context, cmd *cobra.Command, args []string, inBuf *buf // If we're using ledger, only thing we need is the path and the bech32 prefix. if useLedger { bech32PrefixAccAddr := sdk.GetConfig().GetBech32AccountAddrPrefix() - info, err := kb.SaveLedgerKey(name, hd.Secp256k1, bech32PrefixAccAddr, coinType, account, index) + info, err := kb.SaveLedgerKey(name, algo, bech32PrefixAccAddr, coinType, account, index) if err != nil { return err } diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index febad555df..b18d607322 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -379,14 +379,17 @@ func (ks keystore) SignByAddress(address sdk.Address, msg []byte) ([]byte, types func (ks keystore) SaveLedgerKey(uid string, algo SignatureAlgo, hrp string, coinType, account, index uint32) (Info, error) { if !ks.options.SupportedAlgosLedger.Contains(algo) { - return nil, ErrUnsupportedSigningAlgo + return nil, fmt.Errorf( + "%w: signature algo %s is not defined in the keyring options", + ErrUnsupportedSigningAlgo, algo.Name(), + ) } hdPath := hd.NewFundraiserParams(account, coinType, index) priv, _, err := ledger.NewPrivKeySecp256k1(*hdPath, hrp) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to generate ledger key: %w", err) } return ks.writeLedgerKey(uid, priv.PubKey(), *hdPath, algo.Name()) diff --git a/crypto/keyring/keyring_ledger_test.go b/crypto/keyring/keyring_ledger_test.go index eb7e85f9e8..b940970af1 100644 --- a/crypto/keyring/keyring_ledger_test.go +++ b/crypto/keyring/keyring_ledger_test.go @@ -95,7 +95,8 @@ func TestAltKeyring_SaveLedgerKey(t *testing.T) { // Test unsupported Algo _, err = keyring.SaveLedgerKey("key", notSupportedAlgo{}, "cosmos", 118, 0, 0) - require.EqualError(t, err, ErrUnsupportedSigningAlgo.Error()) + require.Error(t, err) + require.Contains(t, err.Error(), ErrUnsupportedSigningAlgo.Error()) ledger, err := keyring.SaveLedgerKey("some_account", hd.Secp256k1, "cosmos", 118, 3, 1) if err != nil { @@ -103,6 +104,7 @@ func TestAltKeyring_SaveLedgerKey(t *testing.T) { t.Skip("ledger nano S: support for ledger devices is not available in this executable") return } + // The mock is available, check that the address is correct require.Equal(t, "some_account", ledger.GetName()) pubKey := ledger.GetPubKey() diff --git a/crypto/ledger/ledger_secp256k1.go b/crypto/ledger/ledger_secp256k1.go index 659181d306..ee2f873544 100644 --- a/crypto/ledger/ledger_secp256k1.go +++ b/crypto/ledger/ledger_secp256k1.go @@ -73,13 +73,13 @@ func NewPrivKeySecp256k1Unsafe(path hd.BIP44Params) (types.LedgerPrivKey, error) func NewPrivKeySecp256k1(path hd.BIP44Params, hrp string) (types.LedgerPrivKey, string, error) { device, err := getDevice() if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("failed to retrieve device: %w", err) } defer warnIfErrors(device.Close) pubKey, addr, err := getPubKeyAddrSafe(device, path, hrp) if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("failed to recover pubkey: %w", err) } return PrivKeyLedgerSecp256k1{pubKey, path}, addr, nil @@ -261,7 +261,7 @@ func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (types.PubKey, error func getPubKeyAddrSafe(device SECP256K1, path hd.BIP44Params, hrp string) (types.PubKey, string, error) { publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp) if err != nil { - return nil, "", fmt.Errorf("address %s rejected", addr) + return nil, "", fmt.Errorf("%w: address rejected for path %s", err, path.String()) } // re-serialize in the 33-byte compressed format diff --git a/docs/versions b/docs/versions index d5dc85eb28..d012300808 100644 --- a/docs/versions +++ b/docs/versions @@ -1,2 +1,4 @@ master master launchpad/backports v0.39 +release/v0.42.x v0.42 +release/v0.43.x v0.43 diff --git a/simapp/app.go b/simapp/app.go index 05f5eb8621..24da97f643 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -221,7 +221,7 @@ func NewSimApp( airdroptypes.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) - memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) + memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, "testing") app := &SimApp{ BaseApp: bApp, @@ -361,7 +361,7 @@ func NewSimApp( // CanWithdrawInvariant invariant. // NOTE: staking module is required if HistoricalEntries param > 0 app.mm.SetOrderBeginBlockers( - upgradetypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, + upgradetypes.ModuleName, capabilitytypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, evidencetypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName, ) app.mm.SetOrderEndBlockers(crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName) diff --git a/types/events.go b/types/events.go index 5a2bf3af4b..27a0017635 100644 --- a/types/events.go +++ b/types/events.go @@ -223,6 +223,11 @@ func toBytes(i interface{}) []byte { // Common event types and attribute keys var ( + EventTypeTx = "tx" + + AttributeKeyAccountSequence = "acc_seq" + AttributeKeySignature = "signature" + EventTypeMessage = "message" AttributeKeyAction = "action" diff --git a/x/auth/ante/sigverify.go b/x/auth/ante/sigverify.go index 79fbbcdc67..6aa0c2a345 100644 --- a/x/auth/ante/sigverify.go +++ b/x/auth/ante/sigverify.go @@ -2,6 +2,7 @@ package ante import ( "bytes" + "encoding/base64" "encoding/hex" "fmt" @@ -90,6 +91,34 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b spkd.ak.SetAccount(ctx, acc) } + // Also emit the following events, so that txs can be indexed by these + // indices: + // - signature (via `tx.signature=''`), + // - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`). + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + + var events sdk.Events + for i, sig := range sigs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signers[i], sig.Sequence)), + )) + + sigBzs, err := signatureDataToBz(sig.Data) + if err != nil { + return ctx, err + } + for _, sigBz := range sigBzs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)), + )) + } + } + + ctx.EventManager().EmitEvents(events) + return next(ctx, tx, simulate) } @@ -436,3 +465,42 @@ func CountSubKeys(pub cryptotypes.PubKey) int { return numKeys } + +// signatureDataToBz converts a SignatureData into raw bytes signature. +// For SingleSignatureData, it returns the signature raw bytes. +// For MultiSignatureData, it returns an array of all individual signatures, +// as well as the aggregated signature. +func signatureDataToBz(data signing.SignatureData) ([][]byte, error) { + if data == nil { + return nil, fmt.Errorf("got empty SignatureData") + } + + switch data := data.(type) { + case *signing.SingleSignatureData: + return [][]byte{data.Signature}, nil + case *signing.MultiSignatureData: + sigs := [][]byte{} + var err error + + for _, d := range data.Signatures { + nestedSigs, err := signatureDataToBz(d) + if err != nil { + return nil, err + } + sigs = append(sigs, nestedSigs...) + } + + multisig := cryptotypes.MultiSignature{ + Signatures: sigs, + } + aggregatedSig, err := multisig.Marshal() + if err != nil { + return nil, err + } + sigs = append(sigs, aggregatedSig) + + return sigs, nil + default: + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unexpected signature data type %T", data) + } +} diff --git a/x/auth/client/cli/cli_test.go b/x/auth/client/cli/cli_test.go index 2b305e8092..c78e03a86a 100644 --- a/x/auth/client/cli/cli_test.go +++ b/x/auth/client/cli/cli_test.go @@ -4,6 +4,7 @@ package cli_test import ( "context" + "encoding/base64" "encoding/json" "fmt" "io/ioutil" @@ -78,11 +79,17 @@ func (s *IntegrationTestSuite) TearDownSuite() { func (s *IntegrationTestSuite) TestCLIValidateSignatures() { val := s.network.Validators[0] - res := s.createBankMsg(val, val.Address) + sendTokens := sdk.NewCoins( + sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)), + sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))) + + res, err := s.createBankMsg(val, val.Address, sendTokens, + fmt.Sprintf("--%s=true", flags.FlagGenerateOnly)) + s.Require().NoError(err) // write unsigned tx to file unsignedTx := testutil.WriteToNewTempFile(s.T(), res.String()) - res, err := authtest.TxSignExec(val.ClientCtx, val.Address, unsignedTx.Name()) + res, err = authtest.TxSignExec(val.ClientCtx, val.Address, unsignedTx.Name()) s.Require().NoError(err) signedTx, err := val.ClientCtx.TxConfig.TxJSONDecoder()(res.Bytes()) s.Require().NoError(err) @@ -104,7 +111,15 @@ func (s *IntegrationTestSuite) TestCLIValidateSignatures() { func (s *IntegrationTestSuite) TestCLISignBatch() { val := s.network.Validators[0] - generatedStd := s.createBankMsg(val, val.Address) + var sendTokens = sdk.NewCoins( + sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)), + sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)), + ) + + generatedStd, err := s.createBankMsg(val, val.Address, + sendTokens, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly)) + s.Require().NoError(err) + outputFile := testutil.WriteToNewTempFile(s.T(), strings.Repeat(generatedStd.String(), 3)) val.ClientCtx.HomeDir = strings.Replace(val.ClientCtx.HomeDir, "simd", "simcli", 1) @@ -136,7 +151,13 @@ func (s *IntegrationTestSuite) TestCLISign_AminoJSON() { require := s.Require() val1 := s.network.Validators[0] txCfg := val1.ClientCtx.TxConfig - txBz := s.createBankMsg(val1, val1.Address) + var sendTokens = sdk.NewCoins( + sdk.NewCoin(fmt.Sprintf("%stoken", val1.Moniker), sdk.NewInt(10)), + sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)), + ) + txBz, err := s.createBankMsg(val1, val1.Address, + sendTokens, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly)) + require.NoError(err) fileUnsigned := testutil.WriteToNewTempFile(s.T(), txBz.String()) chainFlag := fmt.Sprintf("--%s=%s", flags.FlagChainID, val1.ClientCtx.ChainID) sigOnlyFlag := "--signature-only" @@ -224,7 +245,7 @@ func checkSignatures(require *require.Assertions, txCfg client.TxConfig, output } } -func (s *IntegrationTestSuite) TestCLIQueryTxCmd() { +func (s *IntegrationTestSuite) TestCLIQueryTxCmdByHash() { val := s.network.Validators[0] account2, err := val.ClientCtx.Keyring.Key("newAccount2") @@ -271,17 +292,20 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() { expectErr bool rawLogContains string }{ + { + "not enough args", + []string{}, + true, "", + }, { "with invalid hash", []string{"somethinginvalid", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, - true, - "", + true, "", }, { "with valid and not existing hash", []string{"C7E7D3A86A17AB3A321172239F3B61357937AF0F25D9FA4D2F4DCCAD9B0D7747", fmt.Sprintf("--%s=json", tmcli.OutputFlag)}, - true, - "", + true, "", }, { "happy case (legacy Msg)", @@ -318,6 +342,120 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() { } } +func (s *IntegrationTestSuite) TestCLIQueryTxCmdByEvents() { + val := s.network.Validators[0] + + account2, err := val.ClientCtx.Keyring.Key("newAccount2") + s.Require().NoError(err) + + sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10) + + // Send coins. + out, err := s.createBankMsg( + val, account2.GetAddress(), + sdk.NewCoins(sendTokens), + ) + s.Require().NoError(err) + var txRes sdk.TxResponse + s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes)) + s.Require().NoError(s.network.WaitForNextBlock()) + + // Query the tx by hash to get the inner tx. + out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.QueryTxCmd(), []string{txRes.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)}) + s.Require().NoError(err) + s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes)) + protoTx := txRes.GetTx().(*tx.Tx) + + testCases := []struct { + name string + args []string + expectErr bool + expectErrStr string + }{ + { + "invalid --type", + []string{ + fmt.Sprintf("--type=%s", "foo"), + "bar", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, "unknown --type value foo", + }, + { + "--type=acc_seq with no addr+seq", + []string{ + "--type=acc_seq", + "", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, "`acc_seq` type takes an argument '/'", + }, + { + "non-existing addr+seq combo", + []string{ + "--type=acc_seq", + "foobar", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, "found no txs matching given address and sequence combination", + }, + { + "addr+seq happy case", + []string{ + "--type=acc_seq", + fmt.Sprintf("%s/%d", val.Address, protoTx.AuthInfo.SignerInfos[0].Sequence), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, "", + }, + { + "--type=signature with no signature", + []string{ + "--type=signature", + "", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, "argument should be comma-separated signatures", + }, + { + "non-existing signatures", + []string{ + "--type=signature", + "foo", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, "found no txs matching given signatures", + }, + { + "with --signatures happy case", + []string{ + "--type=signature", + base64.StdEncoding.EncodeToString(protoTx.Signatures[0]), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, "", + }, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + cmd := authcli.QueryTxCmd() + clientCtx := val.ClientCtx + + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + if tc.expectErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expectErrStr) + } else { + var result sdk.TxResponse + s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &result)) + s.Require().NotNil(result.Height) + } + }) + } +} + func (s *IntegrationTestSuite) TestCLISendGenerateSignAndBroadcast() { val1 := s.network.Validators[0] @@ -732,7 +870,7 @@ func (s *IntegrationTestSuite) TestCLIMultisign() { sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String()) - // Sign with account1 + // Sign with account2 account2Signature, err := authtest.TxSignExec(val1.ClientCtx, account2.GetAddress(), multiGeneratedTxFile.Name(), "--multisig", multisigInfo.GetAddress().String()) s.Require().NoError(err) @@ -1136,22 +1274,15 @@ func (s *IntegrationTestSuite) TestSignWithMultiSigners_AminoJSON() { require.Equal(sdk.NewCoins(val0Coin, val1Coin), queryRes.Balances) } -func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.AccAddress) testutil.BufferWriter { - res, err := bankcli.MsgSendExec( - val.ClientCtx, - val.Address, - toAddr, - sdk.NewCoins( - sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)), - sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)), - ), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), +func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.AccAddress, amount sdk.Coins, extraFlags ...string) (testutil.BufferWriter, error) { + flags := []string{fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), - ) - s.Require().NoError(err) - return res + fmt.Sprintf("--%s=%s", flags.FlagFees, + sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + flags = append(flags, extraFlags...) + return bankcli.MsgSendExec(val.ClientCtx, val.Address, toAddr, amount, flags...) } func TestIntegrationTestSuite(t *testing.T) { diff --git a/x/auth/client/cli/query.go b/x/auth/client/cli/query.go index 2590858b4c..c136630c2c 100644 --- a/x/auth/client/cli/query.go +++ b/x/auth/client/cli/query.go @@ -11,6 +11,8 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/cosmos/cosmos-sdk/version" authclient "github.com/cosmos/cosmos-sdk/x/auth/client" @@ -19,6 +21,11 @@ import ( const ( flagEvents = "events" + flagType = "type" + + typeHash = "hash" + typeAccSeq = "acc_seq" + typeSig = "signature" eventFormat = "{eventType}.{eventAttribute}={value}" ) @@ -179,28 +186,110 @@ $ %s query txs --%s 'message.sender=cosmos1...&message.action=withdraw_delegator // QueryTxCmd implements the default command for a tx query. func QueryTxCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "tx [hash]", - Short: "Query for a transaction by hash in a committed block", - Args: cobra.ExactArgs(1), + Use: "tx --type=[hash|acc_seq|signature] [hash|acc_seq|signature]", + Short: "Query for a transaction by hash, addr++seq combination or signature in a committed block", + Long: strings.TrimSpace(fmt.Sprintf(` +Example: +$ %s query tx +$ %s query tx --%s=%s : +$ %s query tx --%s=%s +`, + version.AppName, + version.AppName, flagType, typeAccSeq, + version.AppName, flagType, typeSig)), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { return err } - output, err := authclient.QueryTx(clientCtx, args[0]) - if err != nil { - return err - } - if output.Empty() { - return fmt.Errorf("no transaction found with hash %s", args[0]) - } + typ, _ := cmd.Flags().GetString(flagType) + + switch typ { + case typeHash: + { + if args[0] == "" { + return fmt.Errorf("argument should be a tx hash") + } + + // If hash is given, then query the tx by hash. + output, err := authclient.QueryTx(clientCtx, args[0]) + if err != nil { + return err + } - return clientCtx.PrintProto(output) + if output.Empty() { + return fmt.Errorf("no transaction found with hash %s", args[0]) + } + + return clientCtx.PrintProto(output) + } + case typeSig: + { + sigParts, err := parseSigArgs(args) + if err != nil { + return err + } + tmEvents := make([]string, len(sigParts)) + for i, sig := range sigParts { + tmEvents[i] = fmt.Sprintf("%s.%s='%s'", sdk.EventTypeTx, sdk.AttributeKeySignature, sig) + } + + txs, err := authclient.QueryTxsByEvents(clientCtx, tmEvents, rest.DefaultPage, query.DefaultLimit, "") + if err != nil { + return err + } + if len(txs.Txs) == 0 { + return fmt.Errorf("found no txs matching given signatures") + } + if len(txs.Txs) > 1 { + // This case means there's a bug somewhere else in the code. Should not happen. + return errors.Wrapf(errors.ErrLogic, "found %d txs matching given signatures", len(txs.Txs)) + } + + return clientCtx.PrintProto(txs.Txs[0]) + } + case typeAccSeq: + { + if args[0] == "" { + return fmt.Errorf("`acc_seq` type takes an argument '/'") + } + + tmEvents := []string{ + fmt.Sprintf("%s.%s='%s'", sdk.EventTypeTx, sdk.AttributeKeyAccountSequence, args[0]), + } + txs, err := authclient.QueryTxsByEvents(clientCtx, tmEvents, rest.DefaultPage, query.DefaultLimit, "") + if err != nil { + return err + } + if len(txs.Txs) == 0 { + return fmt.Errorf("found no txs matching given address and sequence combination") + } + if len(txs.Txs) > 1 { + // This case means there's a bug somewhere else in the code. Should not happen. + return fmt.Errorf("found %d txs matching given address and sequence combination", len(txs.Txs)) + } + + return clientCtx.PrintProto(txs.Txs[0]) + } + default: + return fmt.Errorf("unknown --%s value %s", flagType, typ) + } }, } flags.AddQueryFlagsToCmd(cmd) + cmd.Flags().String(flagType, typeHash, fmt.Sprintf("The type to be used when querying tx, can be one of \"%s\", \"%s\", \"%s\"", typeHash, typeAccSeq, typeSig)) return cmd } + +// parseSigArgs parses comma-separated signatures from the CLI arguments. +func parseSigArgs(args []string) ([]string, error) { + if len(args) != 1 || args[0] == "" { + return nil, fmt.Errorf("argument should be comma-separated signatures") + } + + return strings.Split(args[0], ","), nil +} diff --git a/x/auth/client/cli/query_test.go b/x/auth/client/cli/query_test.go new file mode 100644 index 0000000000..0168d30081 --- /dev/null +++ b/x/auth/client/cli/query_test.go @@ -0,0 +1,32 @@ +package cli + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseSigs(t *testing.T) { + cases := []struct { + name string + args []string + expErr bool + expNumSigs int + }{ + {"no args", []string{}, true, 0}, + {"empty args", []string{""}, true, 0}, + {"too many args", []string{"foo", "bar"}, true, 0}, + {"1 sig", []string{"foo"}, false, 1}, + {"3 sigs", []string{"foo,bar,baz"}, false, 3}, + } + + for _, tc := range cases { + sigs, err := parseSigArgs(tc.args) + if tc.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tc.expNumSigs, len(sigs)) + } + } +} diff --git a/x/capability/capability_test.go b/x/capability/capability_test.go new file mode 100644 index 0000000000..a197f00dd8 --- /dev/null +++ b/x/capability/capability_test.go @@ -0,0 +1,97 @@ +package capability_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/capability" + "github.com/cosmos/cosmos-sdk/x/capability/keeper" + "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +type CapabilityTestSuite struct { + suite.Suite + + cdc codec.Marshaler + ctx sdk.Context + app *simapp.SimApp + keeper *keeper.Keeper + module module.AppModule +} + +func (suite *CapabilityTestSuite) SetupTest() { + checkTx := false + app := simapp.Setup(checkTx) + cdc := app.AppCodec() + + // create new keeper so we can define custom scoping before init and seal + keeper := keeper.NewKeeper(cdc, app.GetKey(types.StoreKey), app.GetMemKey(types.MemStoreKey)) + + suite.app = app + suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1}) + suite.keeper = keeper + suite.cdc = cdc + suite.module = capability.NewAppModule(cdc, *keeper) +} + +// The following test case mocks a specific bug discovered in https://github.com/cosmos/cosmos-sdk/issues/9800 +// and ensures that the current code successfully fixes the issue. +func (suite *CapabilityTestSuite) TestInitializeMemStore() { + sk1 := suite.keeper.ScopeToModule(banktypes.ModuleName) + + cap1, err := sk1.NewCapability(suite.ctx, "transfer") + suite.Require().NoError(err) + suite.Require().NotNil(cap1) + + // mock statesync by creating new keeper that shares persistent state but loses in-memory map + newKeeper := keeper.NewKeeper(suite.cdc, suite.app.GetKey(types.StoreKey), suite.app.GetMemKey("testing")) + newSk1 := newKeeper.ScopeToModule(banktypes.ModuleName) + + // Mock App startup + ctx := suite.app.BaseApp.NewUncachedContext(false, tmproto.Header{}) + newKeeper.InitializeAndSeal(ctx) + suite.Require().False(newKeeper.IsInitialized(ctx), "memstore initialized flag set before BeginBlock") + + // Mock app beginblock and ensure that no gas has been consumed and memstore is initialized + ctx = suite.app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockGasMeter(sdk.NewGasMeter(50)) + prevGas := ctx.BlockGasMeter().GasConsumed() + restartedModule := capability.NewAppModule(suite.cdc, *newKeeper) + restartedModule.BeginBlock(ctx, abci.RequestBeginBlock{}) + suite.Require().True(newKeeper.IsInitialized(ctx), "memstore initialized flag not set") + gasUsed := ctx.BlockGasMeter().GasConsumed() + + suite.Require().Equal(prevGas, gasUsed, "beginblocker consumed gas during execution") + + // Mock the first transaction getting capability and subsequently failing + // by using a cached context and discarding all cached writes. + cacheCtx, _ := ctx.CacheContext() + _, ok := newSk1.GetCapability(cacheCtx, "transfer") + suite.Require().True(ok) + + // Ensure that the second transaction can still receive capability even if first tx fails. + ctx = suite.app.BaseApp.NewContext(false, tmproto.Header{}) + + cap1, ok = newSk1.GetCapability(ctx, "transfer") + suite.Require().True(ok) + + // Ensure the capabilities don't get reinitialized on next BeginBlock + // by testing to see if capability returns same pointer + // also check that initialized flag is still set + restartedModule.BeginBlock(ctx, abci.RequestBeginBlock{}) + recap, ok := newSk1.GetCapability(ctx, "transfer") + suite.Require().True(ok) + suite.Require().Equal(cap1, recap, "capabilities got reinitialized after second BeginBlock") + suite.Require().True(newKeeper.IsInitialized(ctx), "memstore initialized flag not set") +} + +func TestCapabilityTestSuite(t *testing.T) { + suite.Run(t, new(CapabilityTestSuite)) +} diff --git a/x/capability/genesis.go b/x/capability/genesis.go index ba8e09dcd3..2e9a11b994 100644 --- a/x/capability/genesis.go +++ b/x/capability/genesis.go @@ -13,11 +13,12 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) panic(err) } - // set owners for each index and initialize capability + // set owners for each index for _, genOwner := range genState.Owners { k.SetOwners(ctx, genOwner.Index, genOwner.IndexOwners) - k.InitializeCapability(ctx, genOwner.Index, genOwner.IndexOwners) } + // initialize in-memory capabilities + k.InitMemStore(ctx) } // ExportGenesis returns the capability module's exported genesis. diff --git a/x/capability/genesis_test.go b/x/capability/genesis_test.go new file mode 100644 index 0000000000..34a09960e7 --- /dev/null +++ b/x/capability/genesis_test.go @@ -0,0 +1,59 @@ +package capability_test + +import ( + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/capability" + "github.com/cosmos/cosmos-sdk/x/capability/keeper" + "github.com/cosmos/cosmos-sdk/x/capability/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +func (suite *CapabilityTestSuite) TestGenesis() { + sk1 := suite.keeper.ScopeToModule(banktypes.ModuleName) + sk2 := suite.keeper.ScopeToModule(stakingtypes.ModuleName) + + cap1, err := sk1.NewCapability(suite.ctx, "transfer") + suite.Require().NoError(err) + suite.Require().NotNil(cap1) + + err = sk2.ClaimCapability(suite.ctx, cap1, "transfer") + suite.Require().NoError(err) + + cap2, err := sk2.NewCapability(suite.ctx, "ica") + suite.Require().NoError(err) + suite.Require().NotNil(cap2) + + genState := capability.ExportGenesis(suite.ctx, *suite.keeper) + + // create new app that does not share persistent or in-memory state + // and initialize app from exported genesis state above. + db := dbm.NewMemDB() + encCdc := simapp.MakeTestEncodingConfig() + newApp := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, simapp.DefaultNodeHome, 5, encCdc, simapp.EmptyAppOptions{}) + + newKeeper := keeper.NewKeeper(suite.cdc, newApp.GetKey(types.StoreKey), newApp.GetMemKey(types.MemStoreKey)) + newSk1 := newKeeper.ScopeToModule(banktypes.ModuleName) + newSk2 := newKeeper.ScopeToModule(stakingtypes.ModuleName) + deliverCtx, _ := newApp.BaseApp.NewUncachedContext(false, tmproto.Header{}).WithBlockGasMeter(sdk.NewInfiniteGasMeter()).CacheContext() + + capability.InitGenesis(deliverCtx, *newKeeper, *genState) + + // check that all previous capabilities exist in new app after InitGenesis + sk1Cap1, ok := newSk1.GetCapability(deliverCtx, "transfer") + suite.Require().True(ok, "could not get first capability after genesis on first ScopedKeeper") + suite.Require().Equal(*cap1, *sk1Cap1, "capability values not equal on first ScopedKeeper") + + sk2Cap1, ok := newSk2.GetCapability(deliverCtx, "transfer") + suite.Require().True(ok, "could not get first capability after genesis on first ScopedKeeper") + suite.Require().Equal(*cap1, *sk2Cap1, "capability values not equal on first ScopedKeeper") + + sk2Cap2, ok := newSk2.GetCapability(deliverCtx, "ica") + suite.Require().True(ok, "could not get second capability after genesis on second ScopedKeeper") + suite.Require().Equal(*cap2, *sk2Cap2, "capability values not equal on second ScopedKeeper") +} diff --git a/x/capability/keeper/keeper.go b/x/capability/keeper/keeper.go index b92dfc94ba..4002c199a4 100644 --- a/x/capability/keeper/keeper.go +++ b/x/capability/keeper/keeper.go @@ -13,14 +13,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/capability/types" ) -// initialized is a global variable used by GetCapability to ensure that the memory store -// and capability map are correctly populated. A state-synced node may copy over all the persistent -// state and start running the application without having the in-memory state required for x/capability. -// Thus, we must initialized the memory stores on-the-fly during tx execution once the first GetCapability -// is called. -// This is a temporary fix and should be replaced by a more robust solution in the next breaking release. -var initialized = false - type ( // Keeper defines the capability module's keeper. It is responsible for provisioning, // tracking, and authenticating capabilities at runtime. During application @@ -97,38 +89,58 @@ func (k *Keeper) ScopeToModule(moduleName string) ScopedKeeper { } } -// InitializeAndSeal loads all capabilities from the persistent KVStore into the -// in-memory store and seals the keeper to prevent further modules from creating -// a scoped keeper. InitializeAndSeal must be called once after the application -// state is loaded. +// InitializeAndSeal seals the keeper to prevent further modules from creating +// a scoped keeper. It also panics if the memory store is not of storetype `StoreTypeMemory`. func (k *Keeper) InitializeAndSeal(ctx sdk.Context) { if k.sealed { panic("cannot initialize and seal an already sealed capability keeper") } - memStore := ctx.KVStore(k.memKey) + k.sealed = true +} + +// InitMemStore will initialize the memory store if it hasn't been initialized yet. +// The function is safe to be called multiple times. +// InitMemStore must be called every time the app starts before the keeper is used (so +// `BeginBlock` or `InitChain` - whichever is first). We need access to the store so we +// can't initialize it in a constructor. +func (k *Keeper) InitMemStore(ctx sdk.Context) { + // create context with no block gas meter to ensure we do not consume gas during local initialization logic. + noGasCtx := ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + + memStore := noGasCtx.KVStore(k.memKey) memStoreType := memStore.GetStoreType() if memStoreType != sdk.StoreTypeMemory { panic(fmt.Sprintf("invalid memory store type; got %s, expected: %s", memStoreType, sdk.StoreTypeMemory)) } - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixIndexCapability) - iterator := sdk.KVStorePrefixIterator(prefixStore, nil) + // check if memory store has not been initialized yet by checking if initialized flag is nil. + if !k.IsInitialized(noGasCtx) { + prefixStore := prefix.NewStore(noGasCtx.KVStore(k.storeKey), types.KeyPrefixIndexCapability) + iterator := sdk.KVStorePrefixIterator(prefixStore, nil) - // initialize the in-memory store for all persisted capabilities - defer iterator.Close() + // initialize the in-memory store for all persisted capabilities + defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - index := types.IndexFromKey(iterator.Key()) + for ; iterator.Valid(); iterator.Next() { + index := types.IndexFromKey(iterator.Key()) - var capOwners types.CapabilityOwners + var capOwners types.CapabilityOwners - k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &capOwners) - k.InitializeCapability(ctx, index, capOwners) + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &capOwners) + k.InitializeCapability(noGasCtx, index, capOwners) + } + + // set the initialized flag so we don't rerun initialization logic + memStore.Set(types.KeyMemInitialized, []byte{1}) } +} - k.sealed = true +// IsInitialized returns true if the keeper is properly initialized, and false otherwise +func (k *Keeper) IsInitialized(ctx sdk.Context) bool { + memStore := ctx.KVStore(k.memKey) + return memStore.Get(types.KeyMemInitialized) != nil } // InitializeIndex sets the index to one (or greater) in InitChain according @@ -350,22 +362,6 @@ func (sk ScopedKeeper) ReleaseCapability(ctx sdk.Context, cap *types.Capability) // by name. The module is not allowed to retrieve capabilities which it does not // own. func (sk ScopedKeeper) GetCapability(ctx sdk.Context, name string) (*types.Capability, bool) { - // Create a keeper that will set all in-memory mappings correctly into memstore and capmap if scoped keeper is not initialized yet. - // This ensures that the in-memory mappings are correctly filled in, in case this is a state-synced node. - // This is a temporary non-breaking fix, a future PR should store the reverse mapping in the persistent store and reconstruct forward mapping and capmap on the fly. - if !initialized { - // create context with infinite gas meter to avoid app state mismatch. - initCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - k := Keeper{ - cdc: sk.cdc, - storeKey: sk.storeKey, - memKey: sk.memKey, - capMap: sk.capMap, - } - k.InitializeAndSeal(initCtx) - initialized = true - } - if strings.TrimSpace(name) == "" { return nil, false } diff --git a/x/capability/keeper/keeper_test.go b/x/capability/keeper/keeper_test.go index e62176a724..094b128113 100644 --- a/x/capability/keeper/keeper_test.go +++ b/x/capability/keeper/keeper_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/suite" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -18,6 +19,7 @@ import ( type KeeperTestSuite struct { suite.Suite + cdc codec.Marshaler ctx sdk.Context app *simapp.SimApp keeper *keeper.Keeper @@ -34,6 +36,7 @@ func (suite *KeeperTestSuite) SetupTest() { suite.app = app suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1}) suite.keeper = keeper + suite.cdc = cdc } func (suite *KeeperTestSuite) TestInitializeAndSeal() { diff --git a/x/capability/module.go b/x/capability/module.go index 7957f57747..124712647e 100644 --- a/x/capability/module.go +++ b/x/capability/module.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "math/rand" + "time" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -14,6 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" @@ -136,8 +138,16 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json return cdc.MustMarshalJSON(genState) } -// BeginBlock executes all ABCI BeginBlock logic respective to the capability module. -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} +// BeginBlocker will call InitMemStore to initialize the memory stores in the case +// that this is the first time the node is executing a block since restarting (wiping memory). +// In this case, the BeginBlocker method will reinitialize the memory stores locally, so that subsequent +// capability transactions will pass. +// Otherwise BeginBlocker performs a no-op. +func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + + am.keeper.InitMemStore(ctx) +} // EndBlock executes all ABCI EndBlock logic respective to the capability module. It // returns no validator updates. diff --git a/x/capability/types/keys.go b/x/capability/types/keys.go index 5f171e28a7..aefd13ba22 100644 --- a/x/capability/types/keys.go +++ b/x/capability/types/keys.go @@ -25,6 +25,9 @@ var ( // KeyPrefixIndexCapability defines a key prefix that stores index to capability // name mappings. KeyPrefixIndexCapability = []byte("capability_index") + + // KeyMemInitialized defines the key that stores the initialized flag in the memory store + KeyMemInitialized = []byte("mem_initialized") ) // RevCapabilityKey returns a reverse lookup key for a given module and capability diff --git a/x/distribution/client/cli/tx.go b/x/distribution/client/cli/tx.go index d62bcd21fa..2e6a6eeb39 100644 --- a/x/distribution/client/cli/tx.go +++ b/x/distribution/client/cli/tx.go @@ -24,7 +24,7 @@ var ( ) const ( - MaxMessagesPerTxDefault = 5 + MaxMessagesPerTxDefault = 0 ) // NewTxCmd returns a root CLI command handler for all x/distribution transaction commands. @@ -133,11 +133,12 @@ func NewWithdrawAllRewardsCmd() *cobra.Command { Short: "withdraw all delegations rewards for a delegator", Long: strings.TrimSpace( fmt.Sprintf(`Withdraw all rewards for a single delegator. +Note that if you use this command with --%[2]s=%[3]s or --%[2]s=%[4]s, the %[5]s flag will automatically be set to 0. Example: -$ %s tx distribution withdraw-all-rewards --from mykey +$ %[1]s tx distribution withdraw-all-rewards --from mykey `, - version.AppName, + version.AppName, flags.FlagBroadcastMode, flags.BroadcastSync, flags.BroadcastAsync, FlagMaxMessagesPerTx, ), ), Args: cobra.NoArgs, @@ -177,6 +178,11 @@ $ %s tx distribution withdraw-all-rewards --from mykey } chunkSize, _ := cmd.Flags().GetInt(FlagMaxMessagesPerTx) + if clientCtx.BroadcastMode != flags.BroadcastBlock && chunkSize > 0 { + return fmt.Errorf("cannot use broadcast mode %[1]s with %[2]s != 0", + clientCtx.BroadcastMode, FlagMaxMessagesPerTx) + } + return newSplitAndApply(tx.GenerateOrBroadcastTxCLI, clientCtx, cmd.Flags(), msgs, chunkSize) }, } diff --git a/x/genutil/client/cli/init.go b/x/genutil/client/cli/init.go index 074bb7da4e..f5fda79c69 100644 --- a/x/genutil/client/cli/init.go +++ b/x/genutil/client/cli/init.go @@ -89,11 +89,12 @@ func InitCmd(mbm module.BasicManager, defaultNodeHome string) *cobra.Command { recover, _ := cmd.Flags().GetBool(FlagRecover) if recover { inBuf := bufio.NewReader(cmd.InOrStdin()) - mnemonic, err := input.GetString("Enter your bip39 mnemonic", inBuf) + value, err := input.GetString("Enter your bip39 mnemonic", inBuf) if err != nil { return err } + mnemonic = value if !bip39.IsMnemonicValid(mnemonic) { return errors.New("invalid mnemonic") } diff --git a/x/ibc/core/02-client/keeper/client_test.go b/x/ibc/core/02-client/keeper/client_test.go index 21d59b7402..0f14ef2135 100644 --- a/x/ibc/core/02-client/keeper/client_test.go +++ b/x/ibc/core/02-client/keeper/client_test.go @@ -615,8 +615,8 @@ func (suite *KeeperTestSuite) TestUpdateClientEventEmission() { result, err := suite.chainA.SendMsgs(msg) suite.Require().NoError(err) - // first event type is "message" - updateEvent := result.Events[1] + // first 3 event type are "tx.signature", "tx.acc_seq", and "message" + updateEvent := result.Events[3] suite.Require().Equal(clienttypes.EventTypeUpdateClient, updateEvent.Type)