diff --git a/cmd/auth.go b/cmd/auth.go index 2f4fa37d38..d421d68b18 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -28,6 +28,9 @@ func AuthCmd(fsets ...*flag.FlagSet) *cobra.Command { Short: "Signs and outputs a hex-encoded JWT token with the given permissions.", Long: "Signs and outputs a hex-encoded JWT token with the given permissions. NOTE: only use this command when " + "the node has already been initialized and started.", + PreRunE: func(cmd *cobra.Command, _ []string) error { + return ParseMinimumFlags(cmd) + }, RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 1 { return errors.New("must specify permissions") diff --git a/cmd/celestia/cmd_test.go b/cmd/celestia/cmd_test.go index 94dd3625b8..8cec0e5d97 100644 --- a/cmd/celestia/cmd_test.go +++ b/cmd/celestia/cmd_test.go @@ -3,13 +3,20 @@ package main import ( "bytes" "context" + "io" "os" "reflect" "testing" + "github.com/cristalhq/jwt/v5" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/celestiaorg/celestia-node/api/rpc/perms" "github.com/celestiaorg/celestia-node/header" + "github.com/celestiaorg/celestia-node/libs/authtoken" + "github.com/celestiaorg/celestia-node/libs/keystore" + nodemod "github.com/celestiaorg/celestia-node/nodebuilder/node" ) func TestCompletionHelpString(t *testing.T) { @@ -49,7 +56,7 @@ func TestLight(t *testing.T) { output := &bytes.Buffer{} rootCmd.SetOut(output) rootCmd.SetArgs([]string{ - "bridge", + "light", "--node.store", ".celestia-light", "init", }) @@ -102,6 +109,49 @@ func TestBridge(t *testing.T) { err := rootCmd.ExecuteContext(context.Background()) require.NoError(t, err) }) + // tests that auth admin token generated by node via CLI + // can be verified + contains expected perms + t.Run("auth", func(t *testing.T) { + rootCmd.SetOut(&bytes.Buffer{}) + rootCmd.SetArgs([]string{ + "bridge", + "--node.store", ".celestia-bridge", + "init", + }) + err := rootCmd.ExecuteContext(context.Background()) + require.NoError(t, err) + + ogStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + rootCmd.SetArgs([]string{ + "bridge", + "auth", "admin", + "--node.store", ".celestia-bridge", + }) + + err = rootCmd.ExecuteContext(context.Background()) + require.NoError(t, err) + + err = w.Close() + require.NoError(t, err) + os.Stdout = ogStdout + + ks, err := keystore.NewFSKeystore(".celestia-bridge/keys", nil) + require.NoError(t, err) + key, err := ks.Get(nodemod.SecretName) + require.NoError(t, err) + verifier, err := jwt.NewVerifierHS(jwt.HS256, key.Body) + require.NoError(t, err) + + token, err := io.ReadAll(r) + require.NoError(t, err) + + permissions, err := authtoken.ExtractSignedPermissions(verifier, string(token)) + require.NoError(t, err) + assert.Equal(t, permissions, perms.AllPerms) + }) t.Cleanup(func() { if err := os.Chdir(testDir); err != nil { diff --git a/cmd/celestia/main.go b/cmd/celestia/main.go index 86d31c0ee5..7046376713 100644 --- a/cmd/celestia/main.go +++ b/cmd/celestia/main.go @@ -10,11 +10,21 @@ import ( cmdnode "github.com/celestiaorg/celestia-node/cmd" ) +// WithSubcommands returns the set of commands that require the full flagset. func WithSubcommands() func(*cobra.Command, []*pflag.FlagSet) { return func(c *cobra.Command, flags []*pflag.FlagSet) { c.AddCommand( cmdnode.Init(flags...), cmdnode.Start(cmdnode.WithFlagSet(flags)), + ) + } +} + +// WithAuxiliarySubcommands returns the set of commands that require only the +// minimum flagset for node store determination. +func WithAuxiliarySubcommands() func(*cobra.Command, []*pflag.FlagSet) { + return func(c *cobra.Command, flags []*pflag.FlagSet) { + c.AddCommand( cmdnode.AuthCmd(flags...), cmdnode.ResetStore(flags...), cmdnode.RemoveConfigCmd(flags...), @@ -24,9 +34,9 @@ func WithSubcommands() func(*cobra.Command, []*pflag.FlagSet) { } func init() { - bridgeCmd := cmdnode.NewBridge(WithSubcommands()) - lightCmd := cmdnode.NewLight(WithSubcommands()) - fullCmd := cmdnode.NewFull(WithSubcommands()) + bridgeCmd := cmdnode.NewBridge(WithSubcommands(), WithAuxiliarySubcommands()) + lightCmd := cmdnode.NewLight(WithSubcommands(), WithAuxiliarySubcommands()) + fullCmd := cmdnode.NewFull(WithSubcommands(), WithAuxiliarySubcommands()) rootCmd.AddCommand( bridgeCmd, lightCmd, diff --git a/cmd/config.go b/cmd/config.go index 51bba81b45..c969082e6a 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -12,6 +12,9 @@ func RemoveConfigCmd(fsets ...*flag.FlagSet) *cobra.Command { Use: "config-remove", Short: "Deletes the node's config", Args: cobra.NoArgs, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return ParseMinimumFlags(cmd) + }, RunE: func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() return nodebuilder.RemoveConfig(StorePath(ctx)) @@ -31,6 +34,9 @@ func UpdateConfigCmd(fsets ...*flag.FlagSet) *cobra.Command { Long: "Updates the node's outdated config with default values from newly-added fields. Check the config " + " afterwards to ensure all old custom values were preserved.", Args: cobra.NoArgs, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return ParseMinimumFlags(cmd) + }, RunE: func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() return nodebuilder.UpdateConfig(NodeType(ctx), StorePath(ctx)) diff --git a/cmd/flags_node.go b/cmd/flags_node.go index a6736f8e85..f76d3a89d9 100644 --- a/cmd/flags_node.go +++ b/cmd/flags_node.go @@ -18,8 +18,7 @@ const ( nodeConfigFlag = "node.config" ) -// NodeFlags gives a set of hardcoded Node package flags. -func NodeFlags() *flag.FlagSet { +func NodeStoreFlag() *flag.FlagSet { flags := &flag.FlagSet{} flags.String( @@ -27,6 +26,16 @@ func NodeFlags() *flag.FlagSet { "", "The path to root/home directory of your Celestia Node Store", ) + + return flags +} + +// NodeFlags gives a set of hardcoded Node package flags. +func NodeFlags() *flag.FlagSet { + flags := &flag.FlagSet{} + + flags.AddFlagSet(NodeStoreFlag()) + flags.String( nodeConfigFlag, "", @@ -36,8 +45,7 @@ func NodeFlags() *flag.FlagSet { return flags } -// ParseNodeFlags parses Node flags from the given cmd and applies values to Env. -func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network) (context.Context, error) { +func ParseNodeStore(ctx context.Context, cmd *cobra.Command, network p2p.Network) (context.Context, error) { store := cmd.Flag(nodeStoreFlag).Value.String() if store == "" { tp := NodeType(ctx) @@ -47,7 +55,17 @@ func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network return ctx, err } } + ctx = WithStorePath(ctx, store) + return ctx, nil +} + +// ParseNodeFlags parses Node flags from the given cmd and applies values to Env. +func ParseNodeFlags(ctx context.Context, cmd *cobra.Command, network p2p.Network) (context.Context, error) { + ctx, err := ParseNodeStore(ctx, cmd, network) + if err != nil { + return ctx, err + } nodeConfig := cmd.Flag(nodeConfigFlag).Value.String() if nodeConfig != "" { diff --git a/cmd/init.go b/cmd/init.go index abad602d94..07892ecbf5 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -10,9 +10,10 @@ import ( // Init constructs a CLI command to initialize Celestia Node of any type with the given flags. func Init(fsets ...*flag.FlagSet) *cobra.Command { cmd := &cobra.Command{ - Use: "init", - Short: "Initialization for Celestia Node. Passed flags have persisted effect.", - Args: cobra.NoArgs, + Use: "init", + Short: "Initialization for Celestia Node. Passed flags have persisted effect.", + Args: cobra.NoArgs, + PreRunE: PreRunEnv, RunE: func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() diff --git a/cmd/node.go b/cmd/node.go index 9152eb53e3..ea90ac862b 100644 --- a/cmd/node.go +++ b/cmd/node.go @@ -1,6 +1,9 @@ package cmd import ( + "fmt" + "strings" + "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -14,36 +17,26 @@ import ( "github.com/celestiaorg/celestia-node/nodebuilder/state" ) -func NewBridge(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { - flags := []*pflag.FlagSet{ - NodeFlags(), - p2p.Flags(), - MiscFlags(), - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - pruner.Flags(), - } - cmd := &cobra.Command{ - Use: "bridge [subcommand]", - Args: cobra.NoArgs, - Short: "Manage your Bridge node", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return PersistentPreRunEnv(cmd, node.Bridge, args) - }, - } - for _, option := range options { - option(cmd, flags) - } - return cmd +func NewBridge(addFullFlags, addMinFlags func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { + return createTopLevelCmd(node.Bridge, addFullFlags, addMinFlags) +} + +func NewFull(addFullFlags, addMinFlags func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { + return createTopLevelCmd(node.Full, addFullFlags, addMinFlags) +} + +func NewLight(addFullFlags, addMinFlags func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { + return createTopLevelCmd(node.Light, addFullFlags, addMinFlags) } -func NewLight(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { - flags := []*pflag.FlagSet{ +func createTopLevelCmd( + nodeType node.Type, + addFullFlags, + addMinFlags func(*cobra.Command, []*pflag.FlagSet), +) *cobra.Command { + fullFlags := []*pflag.FlagSet{ NodeFlags(), p2p.Flags(), - header.Flags(), MiscFlags(), core.Flags(), rpc.Flags(), @@ -51,42 +44,27 @@ func NewLight(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command state.Flags(), pruner.Flags(), } - cmd := &cobra.Command{ - Use: "light [subcommand]", - Args: cobra.NoArgs, - Short: "Manage your Light node", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return PersistentPreRunEnv(cmd, node.Light, args) - }, - } - for _, option := range options { - option(cmd, flags) + if nodeType != node.Bridge { + fullFlags = append(fullFlags, header.Flags()) } - return cmd -} -func NewFull(options ...func(*cobra.Command, []*pflag.FlagSet)) *cobra.Command { - flags := []*pflag.FlagSet{ - NodeFlags(), - p2p.Flags(), - header.Flags(), - MiscFlags(), - core.Flags(), - rpc.Flags(), - gateway.Flags(), - state.Flags(), - pruner.Flags(), + minFlags := []*pflag.FlagSet{ + NodeStoreFlag(), + p2p.NetworkFlag(), } + cmd := &cobra.Command{ - Use: "full [subcommand]", + Use: fmt.Sprintf("%s [subcommand]", strings.ToLower(nodeType.String())), Args: cobra.NoArgs, - Short: "Manage your Full node", - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - return PersistentPreRunEnv(cmd, node.Full, args) + Short: fmt.Sprintf("Manage your %s node", strings.ToLower(nodeType.String())), + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + ctx := WithNodeType(cmd.Context(), nodeType) + cmd.SetContext(ctx) }, } - for _, option := range options { - option(cmd, flags) - } + + addFullFlags(cmd, fullFlags) + addMinFlags(cmd, minFlags) + return cmd } diff --git a/cmd/reset_store.go b/cmd/reset_store.go index a839d765ab..a9971643cc 100644 --- a/cmd/reset_store.go +++ b/cmd/reset_store.go @@ -13,6 +13,9 @@ func ResetStore(fsets ...*flag.FlagSet) *cobra.Command { Use: "unsafe-reset-store", Short: "Resets the node's store to a new state. Leaves the keystore and config intact.", Args: cobra.NoArgs, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return ParseMinimumFlags(cmd) + }, RunE: func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() diff --git a/cmd/start.go b/cmd/start.go index 3a71c5a6db..e1bba46deb 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -25,6 +25,7 @@ Options passed on start override configuration options only on start and are not Aliases: []string{"run", "daemon"}, Args: cobra.NoArgs, SilenceUsage: true, + PreRunE: PreRunEnv, RunE: func(cmd *cobra.Command, _ []string) (err error) { ctx := cmd.Context() diff --git a/cmd/util.go b/cmd/util.go index 026002c1d6..a77f294d31 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -75,13 +75,34 @@ func DecodeToBytes(param string) ([]byte, error) { return decoded, nil } -func PersistentPreRunEnv(cmd *cobra.Command, nodeType node.Type, _ []string) error { +func ParseMinimumFlags(cmd *cobra.Command) error { var ( ctx = cmd.Context() err error ) - ctx = WithNodeType(ctx, nodeType) + net, err := p2p.ParseNetwork(cmd) + if err != nil { + return err + } + ctx = WithNetwork(ctx, net) + + ctx, err = ParseNodeStore(ctx, cmd, net) + if err != nil { + return err + } + + cmd.SetContext(ctx) + return nil +} + +func PreRunEnv(cmd *cobra.Command, _ []string) error { + var ( + ctx = cmd.Context() + err error + ) + + nodeType := NodeType(ctx) parsedNetwork, err := p2p.ParseNetwork(cmd) if err != nil { diff --git a/nodebuilder/p2p/flags.go b/nodebuilder/p2p/flags.go index 7c2fc7bfad..f8e0e12a57 100644 --- a/nodebuilder/p2p/flags.go +++ b/nodebuilder/p2p/flags.go @@ -18,10 +18,27 @@ const ( mutualFlag = "p2p.mutual" ) +func NetworkFlag() *flag.FlagSet { + flags := &flag.FlagSet{} + + flags.String( + networkFlag, + DefaultNetwork.String(), + fmt.Sprintf("The name of the network to connect to, e.g. %s. Must be passed on "+ + "both init and start to take effect. Assumes mainnet (%s) unless otherwise specified.", + listAvailableNetworks(), + DefaultNetwork.String()), + ) + + return flags +} + // Flags gives a set of p2p flags. func Flags() *flag.FlagSet { flags := &flag.FlagSet{} + flags.AddFlagSet(NetworkFlag()) + flags.StringSlice( mutualFlag, nil, @@ -30,14 +47,6 @@ Such connection is immune to peer scoring slashing and connection module trimmin Peers must bidirectionally point to each other. (Format: multiformats.io/multiaddr) `, ) - flags.String( - networkFlag, - DefaultNetwork.String(), - fmt.Sprintf("The name of the network to connect to, e.g. %s. Must be passed on "+ - "both init and start to take effect. Assumes mainnet (%s) unless otherwise specified.", - listAvailableNetworks(), - DefaultNetwork.String()), - ) return flags }