From 78793615b066df97be0947a9da100ea4a497599c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 8 Oct 2024 16:32:30 -0500 Subject: [PATCH] refactor(genutil): export genesis with val set implementation (#22178) --- runtime/v2/app.go | 14 -------- runtime/v2/builder.go | 2 +- runtime/v2/manager.go | 10 ++++-- runtime/v2/services/genesis.go | 51 +++++++++++++++++++++++----- simapp/v2/app_di.go | 10 +++--- simapp/v2/app_test.go | 5 ++- simapp/v2/export.go | 61 ++++++++++++---------------------- 7 files changed, 80 insertions(+), 73 deletions(-) diff --git a/runtime/v2/app.go b/runtime/v2/app.go index e43fac0a3af6..d5999f3bd99f 100644 --- a/runtime/v2/app.go +++ b/runtime/v2/app.go @@ -1,7 +1,6 @@ package runtime import ( - "context" "encoding/json" runtimev2 "cosmossdk.io/api/cosmos/app/runtime/v2" @@ -90,11 +89,6 @@ func (a *App[T]) Close() error { return nil } -// GetStore returns the app store. -func (a *App[T]) GetStore() Store { - return a.db -} - func (a *App[T]) GetAppManager() *appmanager.AppManager[T] { return a.AppManager } @@ -102,11 +96,3 @@ func (a *App[T]) GetAppManager() *appmanager.AppManager[T] { func (a *App[T]) GetQueryHandlers() map[string]appmodulev2.Handler { return a.QueryHandlers } - -func (a *App[T]) Query(ctx context.Context, gasLimit, version uint64, req transaction.Msg) (transaction.Msg, error) { - state, err := a.db.StateAt(version) - if err != nil { - return nil, err - } - return a.stf.Query(ctx, state, gasLimit, req) -} diff --git a/runtime/v2/builder.go b/runtime/v2/builder.go index e74d6ff34f22..adb177d41eb0 100644 --- a/runtime/v2/builder.go +++ b/runtime/v2/builder.go @@ -144,7 +144,7 @@ func (a *AppBuilder[T]) Build(opts ...AppBuilderOption[T]) (*App[T], error) { return nil, errors.New("cannot init genesis on non-zero state") } genesisCtx := services.NewGenesisContext(a.branch(zeroState)) - genesisState, err := genesisCtx.Run(ctx, func(ctx context.Context) error { + genesisState, err := genesisCtx.Mutate(ctx, func(ctx context.Context) error { err = a.app.moduleManager.InitGenesisJSON(ctx, genesisJSON, txHandler) if err != nil { return fmt.Errorf("failed to init genesis: %w", err) diff --git a/runtime/v2/manager.go b/runtime/v2/manager.go index 01b0c93971ab..2d6515b81225 100644 --- a/runtime/v2/manager.go +++ b/runtime/v2/manager.go @@ -230,15 +230,17 @@ func (m *MM[T]) ExportGenesisForModules( channels[moduleName] = make(chan genesisResult) go func(moduleI ModuleI, ch chan genesisResult) { genesisCtx := services.NewGenesisContext(stateFactory()) - _, _ = genesisCtx.Run(ctx, func(ctx context.Context) error { + err := genesisCtx.Read(ctx, func(ctx context.Context) error { jm, err := moduleI.ExportGenesis(ctx) if err != nil { - ch <- genesisResult{nil, err} return err } ch <- genesisResult{jm, nil} return nil }) + if err != nil { + ch <- genesisResult{nil, err} + } }(moduleI, channels[moduleName]) } @@ -783,7 +785,9 @@ func messagePassingInterceptor(msg transaction.Msg) grpc.UnaryServerInterceptor } // requestFullNameFromMethodDesc returns the fully-qualified name of the request message and response of the provided service's method. -func requestFullNameFromMethodDesc(sd *grpc.ServiceDesc, method grpc.MethodDesc) (protoreflect.FullName, protoreflect.FullName, error) { +func requestFullNameFromMethodDesc(sd *grpc.ServiceDesc, method grpc.MethodDesc) ( + protoreflect.FullName, protoreflect.FullName, error, +) { methodFullName := protoreflect.FullName(fmt.Sprintf("%s.%s", sd.ServiceName, method.MethodName)) desc, err := gogoproto.HybridResolver.FindDescriptorByName(methodFullName) if err != nil { diff --git a/runtime/v2/services/genesis.go b/runtime/v2/services/genesis.go index 79ebd92852f8..5d09e5c78f74 100644 --- a/runtime/v2/services/genesis.go +++ b/runtime/v2/services/genesis.go @@ -11,6 +11,7 @@ import ( var ( _ store.KVStoreService = (*GenesisKVStoreService)(nil) _ header.Service = (*GenesisHeaderService)(nil) + _ store.KVStore = (*readonlyKVStore)(nil) ) type genesisContextKeyType struct{} @@ -21,28 +22,41 @@ var genesisContextKey = genesisContextKeyType{} // it backs the store.KVStoreService and header.Service interface implementations // defined in this file. type genesisContext struct { - state store.WriterMap + state store.ReaderMap } // NewGenesisContext creates a new genesis context. -func NewGenesisContext(state store.WriterMap) genesisContext { +func NewGenesisContext(state store.ReaderMap) genesisContext { return genesisContext{ state: state, } } -// Run runs the provided function within the genesis context and returns an +// Mutate runs the provided function within the genesis context and returns an // updated store.WriterMap containing the state modifications made during InitGenesis. -func (g *genesisContext) Run( +func (g genesisContext) Mutate( ctx context.Context, fn func(ctx context.Context) error, ) (store.WriterMap, error) { + writerMap, ok := g.state.(store.WriterMap) + if !ok { + return nil, fmt.Errorf("mutate requires a store.WriterMap, got a %T", g.state) + } ctx = context.WithValue(ctx, genesisContextKey, g) err := fn(ctx) if err != nil { return nil, err } - return g.state, nil + return writerMap, nil +} + +// Read runs the provided function within the genesis context. +func (g genesisContext) Read( + ctx context.Context, + fn func(ctx context.Context) error, +) error { + ctx = context.WithValue(ctx, genesisContextKey, g) + return fn(ctx) } // GenesisKVStoreService is a store.KVStoreService implementation that is used during @@ -71,15 +85,24 @@ func (g *GenesisKVStoreService) OpenKVStore(ctx context.Context) store.KVStore { if v == nil { return g.executionService.OpenKVStore(ctx) } - genCtx, ok := v.(*genesisContext) + genCtx, ok := v.(genesisContext) if !ok { panic(fmt.Errorf("unexpected genesis context type: %T", v)) } - state, err := genCtx.state.GetWriter(g.actor) + writerMap, ok := genCtx.state.(store.WriterMap) + if ok { + state, err := writerMap.GetWriter(g.actor) + if err != nil { + panic(err) + } + return state + + } + state, err := genCtx.state.GetReader(g.actor) if err != nil { panic(err) } - return state + return readonlyKVStore{state} } // GenesisHeaderService is a header.Service implementation that is used during @@ -105,3 +128,15 @@ func NewGenesisHeaderService(executionService header.Service) *GenesisHeaderServ executionService: executionService, } } + +type readonlyKVStore struct { + store.Reader +} + +func (r readonlyKVStore) Set(key, value []byte) error { + panic("tried to call Set on a readonly store") +} + +func (r readonlyKVStore) Delete(key []byte) error { + panic("tried to call Delete on a readonly store") +} diff --git a/simapp/v2/app_di.go b/simapp/v2/app_di.go index 33ba98229ee8..29f62defcde6 100644 --- a/simapp/v2/app_di.go +++ b/simapp/v2/app_di.go @@ -13,6 +13,7 @@ import ( "cosmossdk.io/log" "cosmossdk.io/runtime/v2" serverstore "cosmossdk.io/server/v2/store" + "cosmossdk.io/store/v2" "cosmossdk.io/store/v2/root" basedepinject "cosmossdk.io/x/accounts/defaults/base/depinject" lockupdepinject "cosmossdk.io/x/accounts/defaults/lockup/depinject" @@ -39,6 +40,7 @@ type SimApp[T transaction.Tx] struct { appCodec codec.Codec txConfig client.TxConfig interfaceRegistry codectypes.InterfaceRegistry + store store.RootStore // required keepers during wiring // others keepers are all in the app @@ -173,7 +175,8 @@ func NewSimApp[T transaction.Tx]( if err != nil { panic(err) } - _, err = storeBuilder.Build(logger, storeConfig) + + app.store, err = storeBuilder.Build(logger, storeConfig) if err != nil { panic(err) } @@ -216,7 +219,6 @@ func (app *SimApp[T]) TxConfig() client.TxConfig { return app.txConfig } -// GetStore gets the app store. -func (app *SimApp[T]) GetStore() any { - return app.App.GetStore() +func (app *SimApp[T]) GetStore() store.RootStore { + return app.store } diff --git a/simapp/v2/app_test.go b/simapp/v2/app_test.go index 6ee3709ee552..8c705565956c 100644 --- a/simapp/v2/app_test.go +++ b/simapp/v2/app_test.go @@ -20,7 +20,6 @@ import ( sdkmath "cosmossdk.io/math" "cosmossdk.io/runtime/v2" serverv2 "cosmossdk.io/server/v2" - comettypes "cosmossdk.io/server/v2/cometbft/types" serverv2store "cosmossdk.io/server/v2/store" "cosmossdk.io/store/v2/db" banktypes "cosmossdk.io/x/bank/types" @@ -75,7 +74,7 @@ func NewTestApp(t *testing.T) (*SimApp[transaction.Tx], context.Context) { genesisBytes, err := json.Marshal(genesis) require.NoError(t, err) - st := app.GetStore().(comettypes.Store) + st := app.GetStore() ci, err := st.LastCommitID() require.NoError(t, err) @@ -111,7 +110,7 @@ func MoveNextBlock(t *testing.T, app *SimApp[transaction.Tx], ctx context.Contex bz := sha256.Sum256([]byte{}) - st := app.GetStore().(comettypes.Store) + st := app.GetStore() ci, err := st.LastCommitID() require.NoError(t, err) diff --git a/simapp/v2/export.go b/simapp/v2/export.go index e626fde9da20..8cd82867f479 100644 --- a/simapp/v2/export.go +++ b/simapp/v2/export.go @@ -2,64 +2,45 @@ package simapp import ( "context" - "fmt" - stakingtypes "cosmossdk.io/x/staking/types" + "cosmossdk.io/runtime/v2/services" + "cosmossdk.io/x/staking" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - sdk "github.com/cosmos/cosmos-sdk/types" v2 "github.com/cosmos/cosmos-sdk/x/genutil/v2" ) // ExportAppStateAndValidators exports the state of the application for a genesis // file. -func (app *SimApp[T]) ExportAppStateAndValidators(jailAllowedAddrs []string) (v2.ExportedApp, error) { - // as if they could withdraw from the start of the next block +func (app *SimApp[T]) ExportAppStateAndValidators( + jailAllowedAddrs []string, +) (v2.ExportedApp, error) { ctx := context.Background() + var exportedApp v2.ExportedApp latestHeight, err := app.LoadLatestHeight() if err != nil { - return v2.ExportedApp{}, err + return exportedApp, err } genesis, err := app.ExportGenesis(ctx, latestHeight) if err != nil { - return v2.ExportedApp{}, err + return exportedApp, err } - // get the current bonded validators - resp, err := app.Query(ctx, 0, latestHeight, &stakingtypes.QueryValidatorsRequest{ - Status: stakingtypes.BondStatusBonded, - }) - - vals, ok := resp.(*stakingtypes.QueryValidatorsResponse) - if !ok { - return v2.ExportedApp{}, fmt.Errorf("invalid response, expected QueryValidatorsResponse") + readerMap, err := app.GetStore().StateAt(latestHeight) + if err != nil { + return exportedApp, err } - - // convert to genesis validator - var genesisVals []sdk.GenesisValidator - for _, val := range vals.Validators { - pk, err := val.ConsPubKey() - if err != nil { - return v2.ExportedApp{}, err - } - jsonPk, err := cryptocodec.PubKeyFromProto(pk) - if err != nil { - return v2.ExportedApp{}, err - } - - genesisVals = append(genesisVals, sdk.GenesisValidator{ - Address: sdk.ConsAddress(pk.Address()).Bytes(), - PubKey: jsonPk, - Power: val.GetConsensusPower(app.StakingKeeper.PowerReduction(ctx)), - Name: val.Description.Moniker, - }) + genesisCtx := services.NewGenesisContext(readerMap) + err = genesisCtx.Read(ctx, func(ctx context.Context) error { + exportedApp.Validators, err = staking.WriteValidators(ctx, app.StakingKeeper) + return err + }) + if err != nil { + return exportedApp, err } - return v2.ExportedApp{ - AppState: genesis, - Height: int64(latestHeight), - Validators: genesisVals, - }, err + exportedApp.AppState = genesis + exportedApp.Height = int64(latestHeight) + return exportedApp, nil }