mukan-ignite/ignite/cmd/chain_serve.go
Mukan Erkin Törük c32551b6f7
Some checks failed
Docs Deploy / build_and_deploy (push) Has been cancelled
Generate Docs / cli (push) Has been cancelled
Generate Config Doc / cli (push) Has been cancelled
Go formatting / go-formatting (push) Has been cancelled
Check links / markdown-link-check (push) Has been cancelled
Integration / pre-test (push) Has been cancelled
Integration / test on (push) Has been cancelled
Integration / status (push) Has been cancelled
Lint / Lint Go code (push) Has been cancelled
Test / test (ubuntu-latest) (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:24 +03:00

276 lines
8.2 KiB
Go

package ignitecmd
import (
"context"
"io"
"os"
"os/signal"
"syscall"
tea "github.com/charmbracelet/bubbletea"
"github.com/spf13/cobra"
"golang.org/x/term"
cmdmodel "git.cw.tr/mukan-network/mukan-ignite/ignite/cmd/bubblemodel"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cliui"
uilog "git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cliui/log"
cliuimodel "git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/cliui/model"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/errors"
"git.cw.tr/mukan-network/mukan-ignite/ignite/pkg/events"
"git.cw.tr/mukan-network/mukan-ignite/ignite/services/chain"
)
const (
flagVerbose = "verbose"
flagConfig = "config"
flagForceReset = "force-reset"
flagGenerateClients = "generate-clients"
flagQuitOnFail = "quit-on-fail"
flagResetOnce = "reset-once"
flagOutputFile = "output-file"
)
var isTerminal = term.IsTerminal
// NewChainServe creates a new serve command to serve a blockchain.
func NewChainServe() *cobra.Command {
c := &cobra.Command{
Use: "serve",
Short: "Start a blockchain node in development",
Long: `The serve command compiles and installs the binary (like "ignite chain build"),
uses that binary to initialize the blockchain's data directory for one validator
(like "ignite chain init"), and starts the node locally for development purposes
with automatic code reloading.
Automatic code reloading means Ignite starts watching the project directory.
Whenever a file change is detected, Ignite automatically rebuilds, reinitializes
and restarts the node.
Whenever possible Ignite will try to keep the current state of the chain by
exporting and importing the genesis file.
To force Ignite to start from a clean slate even if a genesis file exists, use
the following flag:
ignite chain serve --reset-once
To force Ignite to reset the state every time the source code is modified, use
the following flag:
ignite chain serve --force-reset
With Ignite it's possible to start more than one blockchain from the same source
code using different config files. This is handy if you're building
inter-blockchain functionality and, for example, want to try sending packets
from one blockchain to another. To start a node using a specific config file:
ignite chain serve --config mars.yml
The serve command is meant to be used ONLY FOR DEVELOPMENT PURPOSES. Under the
hood, it runs "appd start", where "appd" is the name of your chain's binary. For
production, you may want to run "appd start" manually.
`,
Args: cobra.NoArgs,
RunE: chainServeHandler,
}
flagSetPath(c)
flagSetClearCache(c)
c.Flags().AddFlagSet(flagSetHome())
c.Flags().AddFlagSet(flagSetCheckDependencies())
c.Flags().AddFlagSet(flagSetSkipProto())
c.Flags().AddFlagSet(flagSetSkipBuild())
c.Flags().AddFlagSet(flagSetVerbose())
c.Flags().BoolP(flagForceReset, "f", false, "force reset of the app state on start and every source change")
c.Flags().BoolP(flagResetOnce, "r", false, "reset the app state once on init")
c.Flags().Bool(flagGenerateClients, false, "generate code for the configured clients on reset or source code change")
c.Flags().Bool(flagQuitOnFail, false, "quit program if the app fails to start")
c.Flags().StringSlice(flagBuildTags, []string{}, "parameters to build the chain binary")
c.Flags().StringP(flagOutputFile, "o", "", "output file logging the chain output (no UI, no stdin, listens for SIGTERM, implies --yes) (default: stdout)")
return c
}
func chainServeHandler(cmd *cobra.Command, _ []string) error {
if shouldRunServeInDaemonMode(cmd) {
return daemonMode(cmd)
}
options := []cliui.Option{cliui.WithoutUserInteraction(getYes(cmd))}
// Session must not handle events when the verbosity is the default
// to allow render of the UI and events using bubbletea. The custom
// UI is not used for other verbosity levels in which the session
// must handle the events to use custom output prefixes.
verbosity := getVerbosity(cmd)
if verbosity == uilog.VerbosityDefault {
options = append(options, cliui.IgnoreEvents())
} else {
options = append(options, cliui.WithVerbosity(verbosity))
}
session := cliui.New(options...)
defer session.End()
// Depending on the verbosity execute the serve command within
// a bubbletea context to display the custom UI.
if verbosity == uilog.VerbosityDefault {
bus := session.EventBus()
bus.Send("Initializing...", events.ProgressStart())
// Render UI
m := cmdmodel.NewChainServe(cmd, bus, chainServeCmd(cmd, session))
_, err := tea.NewProgram(m, tea.WithInput(cmd.InOrStdin())).Run()
return err
}
// Otherwise run the serve command directly
return chainServe(cmd, session)
}
func shouldRunServeInDaemonMode(cmd *cobra.Command) bool {
if cmd.Flags().Changed(flagOutputFile) {
return true
}
return !hasTerminalInputAndOutput(cmd.InOrStdin(), cmd.OutOrStdout())
}
func hasTerminalInputAndOutput(stdin io.Reader, stdout io.Writer) bool {
stdinFD, ok := fileDescriptor(stdin)
if !ok {
return false
}
stdoutFD, ok := fileDescriptor(stdout)
if !ok {
return false
}
return isTerminal(stdinFD) && isTerminal(stdoutFD)
}
type fileDescriptorProvider interface {
Fd() uintptr
}
func fileDescriptor(v any) (int, bool) {
provider, ok := v.(fileDescriptorProvider)
if !ok {
return 0, false
}
return int(provider.Fd()), true
}
// daemonMode runs the chain serve command without user interaction, UI in verbose mode. Useful to be used as daemon.
func daemonMode(cmd *cobra.Command) error {
// always yes, no user interaction
options := []cliui.Option{cliui.WithoutUserInteraction(true)}
options = append(options,
cliui.WithVerbosity(uilog.VerbosityVerbose),
)
// output file logic
outputFile, _ := cmd.Flags().GetString(flagOutputFile)
var output *os.File
var err error
if outputFile != "" {
output, err = os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
if err != nil {
return err
}
defer output.Close()
options = append(options, cliui.WithStdout(output), cliui.WithStderr(output))
} else {
options = append(options, cliui.WithStdout(os.Stdout), cliui.WithStderr(os.Stderr))
}
session := cliui.New(options...)
defer session.End()
ctx, stop := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM)
defer stop()
cmd.SetContext(ctx)
return chainServe(cmd, session)
}
func chainServeCmd(cmd *cobra.Command, session *cliui.Session) tea.Cmd {
return func() tea.Msg {
if err := chainServe(cmd, session); err != nil && !errors.Is(err, context.Canceled) {
return cliuimodel.ErrorMsg{Error: err}
}
return cliuimodel.QuitMsg{}
}
}
func chainServe(cmd *cobra.Command, session *cliui.Session) error {
chainOption := []chain.Option{
chain.WithOutputer(session),
chain.CollectEvents(session.EventBus()),
chain.CheckCosmosSDKVersion(),
}
if flagGetCheckDependencies(cmd) {
chainOption = append(chainOption, chain.CheckDependencies())
}
// check if custom config is defined
config, _ := cmd.Flags().GetString(flagConfig)
if config != "" {
chainOption = append(chainOption, chain.ConfigFile(config))
}
// create the chain
c, err := chain.NewWithHomeFlags(cmd, chainOption...)
if err != nil {
return err
}
cacheStorage, err := newCache(cmd)
if err != nil {
return err
}
// serve the chain
var serveOptions []chain.ServeOption
forceUpdate, _ := cmd.Flags().GetBool(flagForceReset)
if forceUpdate {
serveOptions = append(serveOptions, chain.ServeForceReset())
}
resetOnce, _ := cmd.Flags().GetBool(flagResetOnce)
if resetOnce {
serveOptions = append(serveOptions, chain.ServeResetOnce())
}
quitOnFail, _ := cmd.Flags().GetBool(flagQuitOnFail)
if quitOnFail {
serveOptions = append(serveOptions, chain.QuitOnFail())
}
generateClients, _ := cmd.Flags().GetBool(flagGenerateClients)
if generateClients {
serveOptions = append(serveOptions, chain.GenerateClients())
}
buildTags, _ := cmd.Flags().GetStringSlice(flagBuildTags)
if len(buildTags) > 0 {
serveOptions = append(serveOptions, chain.BuildTags(buildTags...))
}
if flagGetSkipProto(cmd) {
serveOptions = append(serveOptions, chain.ServeSkipProto())
}
if flagGetSkipBuild(cmd) {
serveOptions = append(serveOptions, chain.ServeSkipBuild())
}
if quitOnFail {
serveOptions = append(serveOptions, chain.QuitOnFail())
}
return c.Serve(cmd.Context(), cacheStorage, serveOptions...)
}