From f5080d0dbc627f0dabedf1b26788d41f6aae5782 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 4 Aug 2021 13:51:39 +0200 Subject: [PATCH] fix!: Capability Issue on Restart, Backport to v0.43 (backport #9836) (#9841) * fix!: Capability Issue on Restart, Backport to v0.43 (#9836) ## Description Closes: #9800 The following is a breaking fix to #9800. In the non-breaking fix, the initialization logic was moved out of `InitializeAndSeal` but the API was preserved. I've now removed `InitializeAndSeal` and replaced it with just the `Seal` function, which may be called much more cleanly in `app.go` --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [x] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) (cherry picked from commit 7f316adb97706d82ac5de2bae608eb5ed42300ec) # Conflicts: # CHANGELOG.md * solve conflicts Co-authored-by: Aditya Co-authored-by: Robert Zaremba --- CHANGELOG.md | 6 +- simapp/app.go | 127 +++++--------------------------- x/capability/abci.go | 21 ++++++ x/capability/capability_test.go | 97 ++++++++++++++++++++++++ x/capability/genesis_test.go | 59 +++++++++++++++ 5 files changed, 202 insertions(+), 108 deletions(-) create mode 100644 x/capability/abci.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 54edcf04dd87..0d083130e5be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#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='/'`). +### API Breaking Changes + +* (x/capability) [\#9836](https://github.com/cosmos/cosmos-sdk/pull/9836) Removed `InitializeAndSeal(ctx sdk.Context)` and replaced with `Seal()`. + ### Improvements * (cli) [\#9717](https://github.com/cosmos/cosmos-sdk/pull/9717) Added CLI flag `--output json/text` to `tx` cli commands. @@ -57,7 +61,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Client-Breaking Changes -* [\#9785](https://github.com/cosmos/cosmos-sdk/issues/9785) Missing coin denomination in logs +* [\#9785](https://github.com/cosmos/cosmos-sdk/issues/9785) Missing coin denomination in logs ### CLI Breaking Changes diff --git a/simapp/app.go b/simapp/app.go index b02c5e14599f..66e8295c3c56 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -15,72 +15,10 @@ import ( cmted25519 "github.com/cometbft/cometbft/crypto/ed25519" "github.com/cosmos/gogoproto/proto" "github.com/spf13/cast" - - autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" - reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" - "cosmossdk.io/client/v2/autocli" - clienthelpers "cosmossdk.io/client/v2/helpers" - coreaddress "cosmossdk.io/core/address" - corestore "cosmossdk.io/core/store" - "cosmossdk.io/log" - storetypes "cosmossdk.io/store/types" - "cosmossdk.io/x/accounts" - "cosmossdk.io/x/accounts/accountstd" - baseaccount "cosmossdk.io/x/accounts/defaults/base" - lockup "cosmossdk.io/x/accounts/defaults/lockup" - "cosmossdk.io/x/accounts/testing/account_abstraction" - "cosmossdk.io/x/accounts/testing/counter" - "cosmossdk.io/x/authz" - authzkeeper "cosmossdk.io/x/authz/keeper" - authzmodule "cosmossdk.io/x/authz/module" - "cosmossdk.io/x/bank" - bankkeeper "cosmossdk.io/x/bank/keeper" - banktypes "cosmossdk.io/x/bank/types" - "cosmossdk.io/x/circuit" - circuitkeeper "cosmossdk.io/x/circuit/keeper" - circuittypes "cosmossdk.io/x/circuit/types" - "cosmossdk.io/x/consensus" - consensusparamkeeper "cosmossdk.io/x/consensus/keeper" - consensustypes "cosmossdk.io/x/consensus/types" - distr "cosmossdk.io/x/distribution" - distrkeeper "cosmossdk.io/x/distribution/keeper" - distrtypes "cosmossdk.io/x/distribution/types" - "cosmossdk.io/x/epochs" - epochskeeper "cosmossdk.io/x/epochs/keeper" - epochstypes "cosmossdk.io/x/epochs/types" - "cosmossdk.io/x/evidence" - evidencekeeper "cosmossdk.io/x/evidence/keeper" - evidencetypes "cosmossdk.io/x/evidence/types" - "cosmossdk.io/x/feegrant" - feegrantkeeper "cosmossdk.io/x/feegrant/keeper" - feegrantmodule "cosmossdk.io/x/feegrant/module" - "cosmossdk.io/x/gov" - govkeeper "cosmossdk.io/x/gov/keeper" - govtypes "cosmossdk.io/x/gov/types" - govv1 "cosmossdk.io/x/gov/types/v1" - govv1beta1 "cosmossdk.io/x/gov/types/v1beta1" - "cosmossdk.io/x/group" - groupkeeper "cosmossdk.io/x/group/keeper" - groupmodule "cosmossdk.io/x/group/module" - "cosmossdk.io/x/mint" - mintkeeper "cosmossdk.io/x/mint/keeper" - minttypes "cosmossdk.io/x/mint/types" - "cosmossdk.io/x/nft" - nftkeeper "cosmossdk.io/x/nft/keeper" - nftmodule "cosmossdk.io/x/nft/module" - "cosmossdk.io/x/protocolpool" - poolkeeper "cosmossdk.io/x/protocolpool/keeper" - pooltypes "cosmossdk.io/x/protocolpool/types" - "cosmossdk.io/x/slashing" - slashingkeeper "cosmossdk.io/x/slashing/keeper" - slashingtypes "cosmossdk.io/x/slashing/types" - "cosmossdk.io/x/staking" - stakingkeeper "cosmossdk.io/x/staking/keeper" - stakingtypes "cosmossdk.io/x/staking/types" - "cosmossdk.io/x/tx/signing" - "cosmossdk.io/x/upgrade" - upgradekeeper "cosmossdk.io/x/upgrade/keeper" - upgradetypes "cosmossdk.io/x/upgrade/types" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" + dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" @@ -277,11 +215,10 @@ func NewSimApp( authzkeeper.StoreKey, nftkeeper.StoreKey, group.StoreKey, pooltypes.StoreKey, accounts.StoreKey, epochstypes.StoreKey, ) - - // register streaming services - if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil { - panic(err) - } + tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) + // NOTE: The testingkey is just mounted for testing purposes. Actual applications should + // not include this key. + memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, "testingkey") app := &SimApp{ BaseApp: bApp, @@ -298,8 +235,10 @@ func NewSimApp( app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, runtime.NewEnvironment(runtime.NewKVStoreService(keys[consensustypes.StoreKey]), logger.With(log.ModuleKey, "x/consensus")), govModuleAddr) bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) - // set the version modifier - bApp.SetVersionModifier(consensus.ProvideAppVersionModifier(app.ConsensusParamsKeeper)) + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + // Applications that wish to enforce statically created ScopedKeepers should call `Seal` after creating + // their scoped modules in `NewApp` with `ScopeToModule` + app.CapabilityKeeper.Seal() // add keepers accountsKeeper, err := accounts.NewKeeper( @@ -502,24 +441,10 @@ func NewSimApp( // there is nothing left over in the validator fee pool, so as to keep the // CanWithdrawInvariant invariant. // NOTE: staking module is required if HistoricalEntries param > 0 - app.ModuleManager.SetOrderBeginBlockers( - minttypes.ModuleName, - distrtypes.ModuleName, - pooltypes.ModuleName, - slashingtypes.ModuleName, - evidencetypes.ModuleName, - stakingtypes.ModuleName, - genutiltypes.ModuleName, - authz.ModuleName, - epochstypes.ModuleName, - ) - app.ModuleManager.SetOrderEndBlockers( - govtypes.ModuleName, - stakingtypes.ModuleName, - genutiltypes.ModuleName, - feegrant.ModuleName, - group.ModuleName, - pooltypes.ModuleName, + // NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC) + app.mm.SetOrderBeginBlockers( + upgradetypes.ModuleName, capabilitytypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, + evidencetypes.ModuleName, stakingtypes.ModuleName, ) // NOTE: The genutils module must occur after staking so that pools are @@ -676,22 +601,10 @@ func (app *SimApp) setAnteHandler(txConfig client.TxConfig) { app.SetAnteHandler(anteHandler) } -func (app *SimApp) setPostHandler() { - postHandler, err := posthandler.NewPostHandler( - posthandler.HandlerOptions{}, - ) - if err != nil { - panic(err) - } - - app.SetPostHandler(postHandler) -} - -// Close closes all necessary application resources. -// It implements servertypes.Application. -func (app *SimApp) Close() error { - if err := app.BaseApp.Close(); err != nil { - return err + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + tmos.Exit(err.Error()) + } } return app.UnorderedTxManager.Close() diff --git a/x/capability/abci.go b/x/capability/abci.go new file mode 100644 index 000000000000..9d1c94b5b901 --- /dev/null +++ b/x/capability/abci.go @@ -0,0 +1,21 @@ +package capability + +import ( + "time" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/capability/keeper" + "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +// 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 BeginBlocker(ctx sdk.Context, k keeper.Keeper) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + + k.InitMemStore(ctx) +} diff --git a/x/capability/capability_test.go b/x/capability/capability_test.go new file mode 100644 index 000000000000..45a5f6ea42a8 --- /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.Codec + 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("testingkey")) + newSk1 := newKeeper.ScopeToModule(banktypes.ModuleName) + + // Mock App startup + ctx := suite.app.BaseApp.NewUncachedContext(false, tmproto.Header{}) + newKeeper.Seal() + 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_test.go b/x/capability/genesis_test.go new file mode 100644 index 000000000000..34a09960e750 --- /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") +}