Skip to content

Commit

Permalink
fix!: Capability Issue on Restart, Backport to v0.43 (backport #9836)…
Browse files Browse the repository at this point in the history
… (#9841)

* fix!: Capability Issue on Restart, Backport to v0.43 (#9836)

<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## 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`
  • Loading branch information
mergify[bot] authored and daeMOn63 committed Feb 28, 2022
1 parent 13a7460 commit 571427e
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 59 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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='<base64_sig>'`) or by address and sequence combo (`tx.acc_seq='<addr>/<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.
Expand Down
21 changes: 8 additions & 13 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"

"github.com/cosmos/cosmos-sdk/baseapp"
Expand Down Expand Up @@ -216,7 +215,9 @@ func NewSimApp(
authzkeeper.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey)
memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey, "testing")
// 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,
Expand All @@ -235,6 +236,9 @@ func NewSimApp(
bApp.SetParamStore(app.ParamsKeeper.Subspace(baseapp.Paramspace).WithKeyTable(paramskeeper.ConsensusParamsKeyTable()))

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
app.AccountKeeper = authkeeper.NewAccountKeeper(
Expand Down Expand Up @@ -335,8 +339,9 @@ 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
// NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC)
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,
)
app.mm.SetOrderEndBlockers(crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName)
Expand Down Expand Up @@ -412,16 +417,6 @@ func NewSimApp(
if err := app.LoadLatestVersion(); err != nil {
tmos.Exit(err.Error())
}

// Initialize and seal the capability keeper so all persistent capabilities
// are loaded in-memory and prevent any further modules from creating scoped
// sub-keepers.
// This must be done during creation of baseapp rather than in InitChain so
// that in-memory capabilities get regenerated on app restart.
// Note that since this reads from the store, we can only perform it when
// `loadLatest` is set to true.
ctx := app.BaseApp.NewUncachedContext(true, tmproto.Header{})
app.CapabilityKeeper.InitializeAndSeal(ctx)
}

return app
Expand Down
21 changes: 21 additions & 0 deletions x/capability/abci.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 3 additions & 3 deletions x/capability/capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
type CapabilityTestSuite struct {
suite.Suite

cdc codec.Marshaler
cdc codec.Codec
ctx sdk.Context
app *simapp.SimApp
keeper *keeper.Keeper
Expand Down Expand Up @@ -52,12 +52,12 @@ func (suite *CapabilityTestSuite) TestInitializeMemStore() {
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"))
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.InitializeAndSeal(ctx)
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
Expand Down
58 changes: 19 additions & 39 deletions x/capability/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -97,32 +89,31 @@ func (k *Keeper) ScopeToModule(moduleName string) ScopedKeeper {
}
}

// 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) {
// Seal seals the keeper to prevent further modules from creating a scoped keeper.
// Seal may be called during app initialization for applications that do not wish to create scoped keepers dynamically.
func (k *Keeper) Seal() {
if k.sealed {
panic("cannot initialize and seal an already sealed capability keeper")
}

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.
// InitMemStore will initialize the memory store if the initialized flag is not set.
// InitMemStore logic should be run in first `BeginBlock` or `InitChain` (whichever is run on app start)
// so that the memory store is properly initialized before any transactions are run.
// InitMemStore also asserts the memory store type is correct, and will panic if it does not have store type of `StoreTypeMemory`.
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)
memStore := ctx.KVStore(k.memKey)
memStoreType := memStore.GetStoreType()

if memStoreType != sdk.StoreTypeMemory {
panic(fmt.Sprintf("invalid memory store type; got %s, expected: %s", memStoreType, sdk.StoreTypeMemory))
}

// create context with no block gas meter to ensure we do not consume gas during local initialization logic.
noGasCtx := ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())

// 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)
Expand All @@ -136,12 +127,17 @@ func (k *Keeper) InitMemStore(ctx sdk.Context) {

var capOwners types.CapabilityOwners

k.cdc.MustUnmarshal(iterator.Value(), &capOwners)
k.InitializeCapability(ctx, index, capOwners)
k.cdc.MustUnmarshal(iterator.Value(), &capOwners)
k.InitializeCapability(noGasCtx, index, capOwners)
}

// set the initialized flag so we don't rerun initialization logic
memStore := noGasCtx.KVStore(k.memKey)
memStore.Set(types.KeyMemInitialized, []byte{1})
}
}

// IsInitialized returns true if the keeper is properly initialized, and false otherwise
// IsInitialized returns true if the initialized flag is set, and false otherwise
func (k *Keeper) IsInitialized(ctx sdk.Context) bool {
memStore := ctx.KVStore(k.memKey)
return memStore.Get(types.KeyMemInitialized) != nil
Expand Down Expand Up @@ -366,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
}
Expand Down
6 changes: 3 additions & 3 deletions x/capability/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.cdc = cdc
}

func (suite *KeeperTestSuite) TestInitializeAndSeal() {
func (suite *KeeperTestSuite) TestSeal() {
sk := suite.keeper.ScopeToModule(banktypes.ModuleName)
suite.Require().Panics(func() {
suite.keeper.ScopeToModule(" ")
Expand All @@ -59,7 +59,7 @@ func (suite *KeeperTestSuite) TestInitializeAndSeal() {
}

suite.Require().NotPanics(func() {
suite.keeper.InitializeAndSeal(suite.ctx)
suite.keeper.Seal()
})

for i, cap := range caps {
Expand All @@ -70,7 +70,7 @@ func (suite *KeeperTestSuite) TestInitializeAndSeal() {
}

suite.Require().Panics(func() {
suite.keeper.InitializeAndSeal(suite.ctx)
suite.keeper.Seal()
})

suite.Require().Panics(func() {
Expand Down
4 changes: 3 additions & 1 deletion x/capability/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw
func (AppModule) ConsensusVersion() uint64 { return 1 }

// BeginBlock executes all ABCI BeginBlock logic respective to the capability module.
func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper)
}

// EndBlock executes all ABCI EndBlock logic respective to the capability module. It
// returns no validator updates.
Expand Down

0 comments on commit 571427e

Please sign in to comment.