Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): Allow calling back into the application struct in PostSetup. #24047

Open
wants to merge 8 commits into
base: release/v0.53.x
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* (server) [#24047](https://github.com/cosmos/cosmos-sdk/pull/24047) Allow calling back into the application struct in PostSetup.
* (crypto/ledger) [#24036](https://github.com/cosmos/cosmos-sdk/pull/24036) Improve error message when deriving paths using index > 100
* (gRPC) [#23844](https://github.com/cosmos/cosmos-sdk/pull/23844) Add debug log prints for each gRPC request.
* (server) [#24072](https://github.com/cosmos/cosmos-sdk/pull/24072) Return BlockHeader by shallow copy in server Context.
Expand Down
2 changes: 1 addition & 1 deletion client/pruning/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const FlagAppDBBackend = "app-db-backend"

// Cmd prunes the sdk root multi store history versions based on the pruning options
// specified by command flags.
func Cmd(appCreator servertypes.AppCreator, defaultNodeHome string) *cobra.Command {
func Cmd[T servertypes.Application](appCreator servertypes.AppCreator[T], defaultNodeHome string) *cobra.Command {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hoangdv2429 we do not need these uses of generics in here. Can we remove?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the only diff in this PR is the generics. If that is the case, then we can just close

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the purpose of this pr is to allow calling back into the application struct in PostSetup. If that something we don't want to support in the future for berachain then we can close this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the PR extends the StartCmdOptions struct here: https://github.com/cosmos/cosmos-sdk/pull/24047/files#diff-97289af2ab66016f168df6fccac70626d125f170e5cf0841fcdc8fa624999608R112-R124

the generics help with type safety i believe - so you cannot pass in an AppCreator for a different type than your application.

cmd := &cobra.Command{
Use: "prune [pruning-method]",
Short: "Prune app history states by keeping the recent heights and deleting old heights",
Expand Down
2 changes: 1 addition & 1 deletion client/snapshot/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

// Cmd returns the snapshots group command
func Cmd(appCreator servertypes.AppCreator) *cobra.Command {
func Cmd[T servertypes.Application](appCreator servertypes.AppCreator[T]) *cobra.Command {
cmd := &cobra.Command{
Use: "snapshots",
Short: "Manage local snapshots",
Expand Down
2 changes: 1 addition & 1 deletion client/snapshot/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// ExportSnapshotCmd returns a command to take a snapshot of the application state
func ExportSnapshotCmd(appCreator servertypes.AppCreator) *cobra.Command {
func ExportSnapshotCmd[T servertypes.Application](appCreator servertypes.AppCreator[T]) *cobra.Command {
cmd := &cobra.Command{
Use: "export",
Short: "Export app state to snapshot store",
Expand Down
2 changes: 1 addition & 1 deletion client/snapshot/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

// RestoreSnapshotCmd returns a command to restore a snapshot
func RestoreSnapshotCmd(appCreator servertypes.AppCreator) *cobra.Command {
func RestoreSnapshotCmd[T servertypes.Application](appCreator servertypes.AppCreator[T]) *cobra.Command {
cmd := &cobra.Command{
Use: "restore <height> <format>",
Short: "Restore app state from local snapshot",
Expand Down
2 changes: 1 addition & 1 deletion server/cmt_cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ func QueryBlockResultsCmd() *cobra.Command {
return cmd
}

func BootstrapStateCmd(appCreator types.AppCreator) *cobra.Command {
func BootstrapStateCmd[T types.Application](appCreator types.AppCreator[T]) *cobra.Command {
cmd := &cobra.Command{
Use: "bootstrap-state",
Short: "Bootstrap CometBFT state at an arbitrary block height using a light client",
Expand Down
4 changes: 2 additions & 2 deletions server/module_hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

// ModuleHashByHeightQuery retrieves the module hashes at a given height.
func ModuleHashByHeightQuery(appCreator types.AppCreator) *cobra.Command {
func ModuleHashByHeightQuery[T types.Application](appCreator types.AppCreator[T]) *cobra.Command {
cmd := &cobra.Command{
Use: "module-hash-by-height [height]",
Short: "Get module hashes at a given height",
Expand Down Expand Up @@ -51,7 +51,7 @@ func ModuleHashByHeightQuery(appCreator types.AppCreator) *cobra.Command {
return cmd
}

func getModuleHashesAtHeight(svrCtx *Context, appCreator types.AppCreator, height int64) (*storetypes.CommitInfo, error) {
func getModuleHashesAtHeight[T types.Application](svrCtx *Context, appCreator types.AppCreator[T], height int64) (*storetypes.CommitInfo, error) {
home := svrCtx.Config.RootDir
db, err := openDB(home, GetAppDBBackend(svrCtx.Viper))
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion server/rollback.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// NewRollbackCmd creates a command to rollback CometBFT and multistore state by one height.
func NewRollbackCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
func NewRollbackCmd[T types.Application](appCreator types.AppCreator[T], defaultNodeHome string) *cobra.Command {
var removeBlock bool

cmd := &cobra.Command{
Expand Down
42 changes: 22 additions & 20 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,30 +109,30 @@ const (
)

// StartCmdOptions defines options that can be customized in `StartCmdWithOptions`,
type StartCmdOptions struct {
type StartCmdOptions[T types.Application] struct {
// DBOpener can be used to customize db opening, for example customize db options or support different db backends,
// default to the builtin db opener.
DBOpener func(rootDir string, backendType dbm.BackendType) (dbm.DB, error)
// PostSetup can be used to setup extra services under the same cancellable context,
// it's not called in stand-alone mode, only for in-process mode.
PostSetup func(svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error
PostSetup func(app T, svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error
// PostSetupStandalone can be used to setup extra services under the same cancellable context,
PostSetupStandalone func(svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error
PostSetupStandalone func(app T, svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error
// AddFlags add custom flags to start cmd
AddFlags func(cmd *cobra.Command)
// StartCommandHanlder can be used to customize the start command handler
StartCommandHandler func(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator, inProcessConsensus bool, opts StartCmdOptions) error
StartCommandHandler func(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator[T], inProcessConsensus bool, opts StartCmdOptions[T]) error
Comment on lines +118 to +124
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain what the benefit is for app devs with this? i saw you mentioned berachain, but curious what they/others use it for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beside allowing PostSetup to access and manipulate app instance directly in the setup process. It wasn't answer in the original PR. Look like a hook that allows adding custom logic after the setup is complete but before the app starts running. Like initializing additional components, setting up background processes (data metrics collect ??), configuring middleware..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure 🤔 also I can't tag him

}

// StartCmd runs the service passed in, either stand-alone or in-process with
// CometBFT.
func StartCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
return StartCmdWithOptions(appCreator, defaultNodeHome, StartCmdOptions{})
func StartCmd[T types.Application](appCreator types.AppCreator[T], defaultNodeHome string) *cobra.Command {
return StartCmdWithOptions(appCreator, defaultNodeHome, StartCmdOptions[T]{})
}

// StartCmdWithOptions runs the service passed in, either stand-alone or in-process with
// CometBFT.
func StartCmdWithOptions(appCreator types.AppCreator, defaultNodeHome string, opts StartCmdOptions) *cobra.Command {
func StartCmdWithOptions[T types.Application](appCreator types.AppCreator[T], defaultNodeHome string, opts StartCmdOptions[T]) *cobra.Command {
if opts.DBOpener == nil {
opts.DBOpener = openDB
}
Expand Down Expand Up @@ -210,7 +210,7 @@ is performed. Note, when enabled, gRPC will also be automatically enabled.
return cmd
}

func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator, withCmt bool, opts StartCmdOptions) error {
func start[T types.Application](svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator[T], withCmt bool, opts StartCmdOptions[T]) error {
svrCfg, err := getAndValidateConfig(svrCtx)
if err != nil {
return err
Expand All @@ -235,7 +235,7 @@ func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreato
return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts)
}

func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, metrics *telemetry.Metrics, opts StartCmdOptions) error {
func startStandAlone[T types.Application](svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app T, metrics *telemetry.Metrics, opts StartCmdOptions[T]) error {
addr := svrCtx.Viper.GetString(flagAddress)
transport := svrCtx.Viper.GetString(flagTransport)

Expand Down Expand Up @@ -280,7 +280,7 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie
}

if opts.PostSetupStandalone != nil {
if err := opts.PostSetupStandalone(svrCtx, clientCtx, ctx, g); err != nil {
if err := opts.PostSetupStandalone(app, svrCtx, clientCtx, ctx, g); err != nil {
return err
}
}
Expand All @@ -301,8 +301,8 @@ func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clie
return g.Wait()
}

func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application,
metrics *telemetry.Metrics, opts StartCmdOptions,
func startInProcess[T types.Application](svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app T,
metrics *telemetry.Metrics, opts StartCmdOptions[T],
) error {
cmtCfg := svrCtx.Config
gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly)
Expand Down Expand Up @@ -346,7 +346,7 @@ func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx clien
}

if opts.PostSetup != nil {
if err := opts.PostSetup(svrCtx, clientCtx, ctx, g); err != nil {
if err := opts.PostSetup(app, svrCtx, clientCtx, ctx, g); err != nil {
return err
}
}
Expand Down Expand Up @@ -592,7 +592,7 @@ func getCtx(svrCtx *Context, block bool) (*errgroup.Group, context.Context) {
return g, ctx
}

func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions) (app types.Application, cleanupFn func(), err error) {
func startApp[T types.Application](svrCtx *Context, appCreator types.AppCreator[T], opts StartCmdOptions[T]) (app T, cleanupFn func(), err error) {
traceWriter, traceCleanupFn, err := setupTraceWriter(svrCtx)
if err != nil {
return app, traceCleanupFn, err
Expand All @@ -605,10 +605,12 @@ func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions
}

if isTestnet, ok := svrCtx.Viper.Get(KeyIsTestnet).(bool); ok && isTestnet {
app, err = testnetify(svrCtx, appCreator, db, traceWriter)
var appPtr *T
appPtr, err = testnetify(svrCtx, appCreator, db, traceWriter)
if err != nil {
return app, traceCleanupFn, err
}
app = *appPtr
} else {
app = appCreator(svrCtx.Logger, db, traceWriter, svrCtx.Viper)
}
Expand All @@ -625,8 +627,8 @@ func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions
// InPlaceTestnetCreator utilizes the provided chainID and operatorAddress as well as the local private validator key to
// control the network represented in the data folder. This is useful to create testnets nearly identical to your
// mainnet environment.
func InPlaceTestnetCreator(testnetAppCreator types.AppCreator) *cobra.Command {
opts := StartCmdOptions{}
func InPlaceTestnetCreator[T types.Application](testnetAppCreator types.AppCreator[T]) *cobra.Command {
opts := StartCmdOptions[T]{}
if opts.DBOpener == nil {
opts.DBOpener = openDB
}
Expand Down Expand Up @@ -722,7 +724,7 @@ you want to test the upgrade handler itself.

// testnetify modifies both state and blockStore, allowing the provided operator address and local validator key to control the network
// that the state in the data folder represents. The chainID of the local genesis file is modified to match the provided chainID.
func testnetify(ctx *Context, testnetAppCreator types.AppCreator, db dbm.DB, traceWriter io.WriteCloser) (types.Application, error) {
func testnetify[T types.Application](ctx *Context, testnetAppCreator types.AppCreator[T], db dbm.DB, traceWriter io.WriteCloser) (*T, error) {
config := ctx.Config

newChainID, ok := ctx.Viper.Get(KeyNewChainID).(string)
Expand Down Expand Up @@ -952,11 +954,11 @@ func testnetify(ctx *Context, testnetAppCreator types.AppCreator, db dbm.DB, tra
return nil, err
}

return testnetApp, err
return &testnetApp, err
}

// addStartNodeFlags should be added to any CLI commands that start the network.
func addStartNodeFlags(cmd *cobra.Command, opts StartCmdOptions) {
func addStartNodeFlags[T types.Application](cmd *cobra.Command, opts StartCmdOptions[T]) {
cmd.Flags().Bool(flagWithComet, true, "Run abci app embedded in-process with CometBFT")
cmd.Flags().String(flagAddress, "tcp://127.0.0.1:26658", "Listen address")
cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc")
Expand Down
2 changes: 1 addition & 1 deletion server/types/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type (

// AppCreator is a function that allows us to lazily initialize an
// application using various configurations.
AppCreator func(log.Logger, dbm.DB, io.Writer, AppOptions) Application
AppCreator[T Application] func(log.Logger, dbm.DB, io.Writer, AppOptions) T

// ModuleInitFlags takes a start command and adds modules specific init flags.
ModuleInitFlags func(startCmd *cobra.Command)
Expand Down
6 changes: 3 additions & 3 deletions server/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func interceptConfigs(rootViper *viper.Viper, customAppTemplate string, customCo
}

// add server commands
func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator types.AppCreator, appExport types.AppExporter, addStartFlags types.ModuleInitFlags) {
func AddCommands[T types.Application](rootCmd *cobra.Command, defaultNodeHome string, appCreator types.AppCreator[T], appExport types.AppExporter, addStartFlags types.ModuleInitFlags) {
cometCmd := &cobra.Command{
Use: "comet",
Aliases: []string{"cometbft", "tendermint"},
Expand Down Expand Up @@ -343,7 +343,7 @@ func AddCommands(rootCmd *cobra.Command, defaultNodeHome string, appCreator type
}

// AddCommandsWithStartCmdOptions adds server commands with the provided StartCmdOptions.
func AddCommandsWithStartCmdOptions(rootCmd *cobra.Command, defaultNodeHome string, appCreator types.AppCreator, appExport types.AppExporter, opts StartCmdOptions) {
func AddCommandsWithStartCmdOptions[T types.Application](rootCmd *cobra.Command, defaultNodeHome string, appCreator types.AppCreator[T], appExport types.AppExporter, opts StartCmdOptions[T]) {
cometCmd := &cobra.Command{
Use: "comet",
Aliases: []string{"cometbft", "tendermint"},
Expand Down Expand Up @@ -372,7 +372,7 @@ func AddCommandsWithStartCmdOptions(rootCmd *cobra.Command, defaultNodeHome stri
}

// AddTestnetCreatorCommand allows chains to create a testnet from the state existing in their node's data directory.
func AddTestnetCreatorCommand(rootCmd *cobra.Command, appCreator types.AppCreator, addStartFlags types.ModuleInitFlags) {
func AddTestnetCreatorCommand[T types.Application](rootCmd *cobra.Command, appCreator types.AppCreator[T], addStartFlags types.ModuleInitFlags) {
testnetCreateCmd := InPlaceTestnetCreator(appCreator)
addStartFlags(testnetCreateCmd)
rootCmd.AddCommand(testnetCreateCmd)
Expand Down
18 changes: 9 additions & 9 deletions server/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestGetAppDBBackend(t *testing.T) {

func TestInterceptConfigsPreRunHandlerCreatesConfigFilesWhenMissing(t *testing.T) {
tempDir := t.TempDir()
cmd := server.StartCmd(nil, "/foobar")
cmd := server.StartCmd[servertypes.Application](nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
Expand Down Expand Up @@ -91,7 +91,7 @@ func TestInterceptConfigsPreRunHandlerCreatesConfigFilesWhenMissing(t *testing.T
}

if !s.Mode().IsRegular() {
t.Fatal("appp.toml not created as regular file")
t.Fatal("app.toml not created as regular file")
}

if s.Size() == 0 {
Expand Down Expand Up @@ -126,7 +126,7 @@ func TestInterceptConfigsPreRunHandlerReadsConfigToml(t *testing.T) {
t.Fatalf("Failed closing config.toml: %v", err)
}

cmd := server.StartCmd(nil, "/foobar")
cmd := server.StartCmd[servertypes.Application](nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
Expand Down Expand Up @@ -166,7 +166,7 @@ func TestInterceptConfigsPreRunHandlerReadsAppToml(t *testing.T) {
if err := writer.Close(); err != nil {
t.Fatalf("Failed closing app.toml: %v", err)
}
cmd := server.StartCmd(nil, tempDir)
cmd := server.StartCmd[servertypes.Application](nil, tempDir)

cmd.PreRunE = preRunETestImpl

Expand All @@ -185,7 +185,7 @@ func TestInterceptConfigsPreRunHandlerReadsAppToml(t *testing.T) {
func TestInterceptConfigsPreRunHandlerReadsFlags(t *testing.T) {
const testAddr = "tcp://127.1.2.3:12345"
tempDir := t.TempDir()
cmd := server.StartCmd(nil, "/foobar")
cmd := server.StartCmd[servertypes.Application](nil, "/foobar")

if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
Expand Down Expand Up @@ -213,7 +213,7 @@ func TestInterceptConfigsPreRunHandlerReadsFlags(t *testing.T) {
func TestInterceptConfigsPreRunHandlerReadsEnvVars(t *testing.T) {
const testAddr = "tcp://127.1.2.3:12345"
tempDir := t.TempDir()
cmd := server.StartCmd(nil, "/foobar")
cmd := server.StartCmd[servertypes.Application](nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
Expand Down Expand Up @@ -301,7 +301,7 @@ func newPrecedenceCommon(t *testing.T) precedenceCommon {
})

// Set up the command object that is used in this test
retval.cmd = server.StartCmd(nil, tempDir)
retval.cmd = server.StartCmd[servertypes.Application](nil, tempDir)
retval.cmd.PreRunE = preRunETestImpl

return retval
Expand Down Expand Up @@ -410,7 +410,7 @@ func TestInterceptConfigsWithBadPermissions(t *testing.T) {
if err := os.Mkdir(subDir, 0o600); err != nil {
t.Fatalf("Failed to create sub directory: %v", err)
}
cmd := server.StartCmd(nil, "/foobar")
cmd := server.StartCmd[servertypes.Application](nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, subDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
Expand Down Expand Up @@ -447,7 +447,7 @@ func TestEmptyMinGasPrices(t *testing.T) {
config.WriteConfigFile(appCfgTempFilePath, appConf)

// Run StartCmd.
cmd = server.StartCmd(nil, tempDir)
cmd = server.StartCmd[servertypes.Application](nil, tempDir)
cmd.PreRunE = func(cmd *cobra.Command, _ []string) error {
ctx, err := server.InterceptConfigsAndCreateContext(cmd, "", nil, cmtcfg.DefaultConfig())
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion simapp/simd/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func initRootCmd(
snapshot.Cmd(newApp),
)

server.AddCommandsWithStartCmdOptions(rootCmd, simapp.DefaultNodeHome, newApp, appExport, server.StartCmdOptions{
server.AddCommandsWithStartCmdOptions(rootCmd, simapp.DefaultNodeHome, newApp, appExport, server.StartCmdOptions[servertypes.Application]{
AddFlags: func(startCmd *cobra.Command) {
},
})
Expand Down
Loading