package ignitecmd import ( "context" "fmt" tea "github.com/charmbracelet/bubbletea" "github.com/spf13/cobra" cmdmodel "github.com/ignite/cli/v29/ignite/cmd/bubblemodel" chainconfig "github.com/ignite/cli/v29/ignite/config/chain" "github.com/ignite/cli/v29/ignite/pkg/chaincmd" "github.com/ignite/cli/v29/ignite/pkg/cliui" "github.com/ignite/cli/v29/ignite/pkg/cliui/icons" cliuimodel "github.com/ignite/cli/v29/ignite/pkg/cliui/model" "github.com/ignite/cli/v29/ignite/pkg/debugger" "github.com/ignite/cli/v29/ignite/pkg/errors" "github.com/ignite/cli/v29/ignite/pkg/events" "github.com/ignite/cli/v29/ignite/pkg/xurl" "github.com/ignite/cli/v29/ignite/services/chain" ) const ( flagServer = "server" flagServerAddress = "server-address" ) // NewChainDebug returns a new debug command to debug a blockchain app. func NewChainDebug() *cobra.Command { c := &cobra.Command{ Use: "debug", Short: "Launch a debugger for a blockchain app", Long: `The debug command starts a debug server and launches a debugger. Ignite uses the Delve debugger by default. Delve enables you to interact with your program by controlling the execution of the process, evaluating variables, and providing information of thread / goroutine state, CPU register state and more. A debug server can optionally be started in cases where default terminal client is not desirable. When the server starts it first runs the blockchain app, attaches to it and finally waits for a client connection. It accepts both JSON-RPC or DAP client connections. To start a debug server use the following flag: ignite chain debug --server To start a debug server with a custom address use the following flags: ignite chain debug --server --server-address 127.0.0.1:30500 The debug server stops automatically when the client connection is closed. `, Args: cobra.NoArgs, RunE: chainDebugHandler, } flagSetPath(c) c.Flags().Bool(flagServer, false, "start a debug server") c.Flags().String(flagServerAddress, debugger.DefaultAddress, "debug server address") return c } func chainDebugHandler(cmd *cobra.Command, _ []string) error { // Prepare session options. // Events are ignored by the session when the debug server UI is used. options := []cliui.Option{ cliui.StartSpinnerWithText("Initializing..."), cliui.WithoutUserInteraction(getYes(cmd)), } server, _ := cmd.Flags().GetBool(flagServer) if server { options = append(options, cliui.IgnoreEvents()) } session := cliui.New(options...) defer session.End() // Start debug server if server { bus := session.EventBus() m := cmdmodel.NewChainDebug(cmd, bus, chainDebugCmd(cmd, session)) _, err := tea.NewProgram(m, tea.WithInput(cmd.InOrStdin())).Run() return err } return chainDebug(cmd, session) } func chainDebugCmd(cmd *cobra.Command, session *cliui.Session) tea.Cmd { return func() tea.Msg { if err := chainDebug(cmd, session); err != nil && !errors.Is(err, context.Canceled) { return cliuimodel.ErrorMsg{Error: err} } return cliuimodel.QuitMsg{} } } func chainDebug(cmd *cobra.Command, session *cliui.Session) error { chainOptions := []chain.Option{ chain.KeyringBackend(chaincmd.KeyringBackendTest), } // check if custom config is defined config, _ := cmd.Flags().GetString(flagConfig) if config != "" { chainOptions = append(chainOptions, chain.ConfigFile(config)) } c, err := chain.NewWithHomeFlags(cmd, chainOptions...) if err != nil { return err } cfg, err := c.Config() if err != nil { return err } validator, err := chainconfig.FirstValidator(cfg) if err != nil { return err } servers, err := validator.GetServers() if err != nil { return err } home, err := c.Home() if err != nil { return err } binPath, err := c.AbsBinaryPath() if err != nil { return err } // Common debugger options debugOptions := []debugger.Option{ debugger.WorkingDir(flagGetPath(cmd)), debugger.BinaryArgs( "start", "--pruning", "nothing", "--grpc.address", servers.GRPC.Address, "--home", home, ), } // Start debug server ctx := cmd.Context() bus := session.EventBus() if server, _ := cmd.Flags().GetBool(flagServer); server { addr, _ := cmd.Flags().GetString(flagServerAddress) tcpAddr, err := xurl.TCP(addr) if err != nil { return err } debugOptions = append(debugOptions, debugger.Address(addr), debugger.ServerStartHook(func() { bus.Send( fmt.Sprintf("Debug server: %s", tcpAddr), events.Icon(icons.Earth), events.ProgressFinish(), ) }), ) bus.Send("Launching debug server", events.ProgressUpdate()) return debugger.Start(ctx, binPath, debugOptions...) } // Launch a debugger client debugOptions = append(debugOptions, debugger.ClientRunHook(func() { // End session to allow debugger to gain control of stdout session.End() }), ) bus.Send("Launching debugger", events.ProgressUpdate()) return debugger.Run(ctx, binPath, debugOptions...) }