diff --git a/client/v2/CHANGELOG.md b/client/v2/CHANGELOG.md index c86146bcb545..dfc4eac2c219 100644 --- a/client/v2/CHANGELOG.md +++ b/client/v2/CHANGELOG.md @@ -36,12 +36,27 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +<<<<<<< HEAD ## [v2.0.0-beta.2] - 2024-XX-XX ### Improvements * (deps) [#19810](https://github.com/cosmos/cosmos-sdk/pull/19810) Upgrade SDK version due to prometheus breaking change. * (deps) [#19810](https://github.com/cosmos/cosmos-sdk/pull/19810) Bump `cosmossdk.io/store` to v1.1.0. +======= + + +### Features + +* [#18626](https://github.com/cosmos/cosmos-sdk/pull/18626) Support for off-chain signing and verification of a file. +* [#18461](https://github.com/cosmos/cosmos-sdk/pull/18461) Support governance proposals. +* [#19039](https://github.com/cosmos/cosmos-sdk/pull/19039) Add support for pubkey in autocli. +* [#20266](https://github.com/cosmos/cosmos-sdk/pull/20266) Ability to override the short description in AutoCLI-generated top-level commands. + +### Improvements + +* [#19646](https://github.com/cosmos/cosmos-sdk/pull/19646) Use keyring from command context. +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) * [#20083](https://github.com/cosmos/cosmos-sdk/pull/20083) Integrate latest version of cosmos-proto and improve version filtering. * [#19618](https://github.com/cosmos/cosmos-sdk/pull/19618) Marshal enum as string in queries. * [#19060](https://github.com/cosmos/cosmos-sdk/pull/19060) Use client context from root (or enhanced) command in autocli commands. @@ -56,6 +71,14 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#19060](https://github.com/cosmos/cosmos-sdk/pull/19060) Simplify key flag parsing logic in flag handler. * [#20033](https://github.com/cosmos/cosmos-sdk/pull/20033) Respect output format from client ctx. +<<<<<<< HEAD +======= +### API Breaking Changes + +* [#19646](https://github.com/cosmos/cosmos-sdk/pull/19646) Remove keyring from `autocli.AppOptions` and `flag.Builder` options. +* [#17709](https://github.com/cosmos/cosmos-sdk/pull/17709) Address codecs have been removed from `autocli.AppOptions` and `flag.Builder`. Instead client/v2 uses the address codecs present in the context (introduced in [#17503](https://github.com/cosmos/cosmos-sdk/pull/17503)). + +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) ## [v2.0.0-beta.1] - 2023-11-07 This is the first tagged version of client/v2. diff --git a/client/v2/README.md b/client/v2/README.md index ea917ce20a7a..2e40db74d287 100644 --- a/client/v2/README.md +++ b/client/v2/README.md @@ -75,10 +75,10 @@ if err := rootCmd.Execute(); err != nil { ### Keyring -`autocli` uses a keyring for key name resolving and signing transactions. Providing a keyring is optional, but if you want to use the `autocli` generated commands to sign transactions, you must provide a keyring. +`autocli` uses a keyring for key name resolving names and signing transactions. :::tip -This provides a better UX as it allows to resolve key names directly from the keyring in all transactions and commands. +AutoCLI provides a better UX than normal CLI as it allows to resolve key names directly from the keyring in all transactions and commands. ```sh q bank balances alice @@ -87,8 +87,9 @@ This provides a better UX as it allows to resolve key names directly from the ke ::: -The keyring to be provided to `client/v2` must match the `client/v2` keyring interface. -The keyring should be provided in the `appOptions` struct as follows, and can be gotten from the client context: +The keyring used for resolving names and signing transactions is provided via the `client.Context`. +The keyring is then converted to the `client/v2/autocli/keyring` interface. +If no keyring is provided, the `autocli` generated command will not be able to sign transactions, but will still be able to query the chain. :::tip The Cosmos SDK keyring and Hubl keyring both implement the `client/v2/autocli/keyring` interface, thanks to the following wrapper: @@ -99,18 +100,6 @@ keyring.NewAutoCLIKeyring(kb) ::: -:::warning -When using AutoCLI the keyring will only be created once and before any command flag parsing. -::: - -```go -// Set the keyring in the appOptions -appOptions.Keyring = keyring - -err := autoCliOpts.EnhanceRootCommand(rootCmd) -... -``` - ## Signing `autocli` supports signing transactions with the keyring. @@ -224,3 +213,71 @@ https://github.com/cosmos/cosmos-sdk/blob/client/v2.0.0-beta.1/client/grpc/cmtse To further enhance your CLI experience with Cosmos SDK-based blockchains, you can use `hubl`. `hubl` is a tool that allows you to query any Cosmos SDK-based blockchain using the new AutoCLI feature of the Cosmos SDK. With `hubl`, you can easily configure a new chain and query modules with just a few simple commands. For more information on `hubl`, including how to configure a new chain and query a module, see the [Hubl documentation](https://docs.cosmos.network/main/tooling/hubl). +<<<<<<< HEAD +======= + +# Off-Chain + +Off-chain functionalities allow you to sign and verify files with two commands: + +* `sign-file` for signing a file. +* `verify-file` for verifying a previously signed file. + +Signing a file will result in a Tx with a `MsgSignArbitraryData` as described in the [Off-chain CIP](https://github.com/cosmos/cips/blob/main/cips/cip-X.md). + +## Sign a file + +To sign a file `sign-file` command offers some helpful flags: + +```text + --encoding string Choose an encoding method for the file content to be added as msg data (no-encoding|base64|hex) (default "no-encoding") + --indent string Choose an indent for the tx (default " ") + --notEmitUnpopulated Don't show unpopulated fields in the tx + --output string Choose an output format for the tx (json|text (default "json") + --output-document string The document will be written to the given file instead of STDOUT +``` + +The `encoding` flag lets you choose how the contents of the file should be encoded. For example: + +* `simd off-chain sign-file alice myFile.json` + + * ```json + { + "@type": "/offchain.MsgSignArbitraryData", + "appDomain": "simd", + "signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu", + "data": "Hello World!\n" + } + ``` + +* `simd off-chain sign-file alice myFile.json --encoding base64` + + * ```json + { + "@type": "/offchain.MsgSignArbitraryData", + "appDomain": "simd", + "signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu", + "data": "SGVsbG8gV29ybGQhCg==" + } + ``` + +* `simd off-chain sign-file alice myFile.json --encoding hex` + + * ```json + { + "@type": "/offchain.MsgSignArbitraryData", + "appDomain": "simd", + "signer": "cosmos1x33fy6rusfprkntvjsfregss7rvsvyy4lkwrqu", + "data": "48656c6c6f20576f726c64210a" + } + ``` + +## Verify a file + +To verify a file only the key name used and the previously signed file are needed. + +```text +➜ simd off-chain verify-file alice signedFile.json +Verification OK! +``` +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) diff --git a/client/v2/autocli/app.go b/client/v2/autocli/app.go index db8adf4e4bae..84634f14db64 100644 --- a/client/v2/autocli/app.go +++ b/client/v2/autocli/app.go @@ -7,8 +7,11 @@ import ( autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" "cosmossdk.io/client/v2/autocli/flag" +<<<<<<< HEAD "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/core/address" +======= +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) "cosmossdk.io/core/appmodule" "cosmossdk.io/depinject" @@ -37,6 +40,7 @@ type AppOptions struct { // module or need to be improved. ModuleOptions map[string]*autocliv1.ModuleOptions `optional:"true"` +<<<<<<< HEAD // AddressCodec is the address codec to use for the app. AddressCodec address.Codec ValidatorAddressCodec runtime.ValidatorAddressCodec @@ -45,6 +49,8 @@ type AppOptions struct { // Keyring is the keyring to use for client/v2. Keyring keyring.Keyring `optional:"true"` +======= +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) // ClientCtx contains the necessary information needed to execute the commands. ClientCtx client.Context } @@ -69,10 +75,16 @@ func (appOptions AppOptions) EnhanceRootCommand(rootCmd *cobra.Command) error { Builder: flag.Builder{ TypeResolver: protoregistry.GlobalTypes, FileResolver: appOptions.ClientCtx.InterfaceRegistry, +<<<<<<< HEAD AddressCodec: appOptions.AddressCodec, ValidatorAddressCodec: appOptions.ValidatorAddressCodec, ConsensusAddressCodec: appOptions.ConsensusAddressCodec, Keyring: appOptions.Keyring, +======= + AddressCodec: appOptions.ClientCtx.AddressCodec, + ValidatorAddressCodec: appOptions.ClientCtx.ValidatorAddressCodec, + ConsensusAddressCodec: appOptions.ClientCtx.ConsensusAddressCodec, +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) }, GetClientConn: func(cmd *cobra.Command) (grpc.ClientConnInterface, error) { return client.GetClientQueryContext(cmd) diff --git a/client/v2/autocli/common.go b/client/v2/autocli/common.go index de6b06d677a2..d3ba1bd44d23 100644 --- a/client/v2/autocli/common.go +++ b/client/v2/autocli/common.go @@ -52,14 +52,18 @@ func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescrip Version: options.Version, } - binder, err := b.AddMessageFlags(cmd.Context(), cmd.Flags(), inputType, options) + // we need to use a pointer to the context as the correct context is set in the RunE function + // however we need to set the flags before the RunE function is called + ctx := cmd.Context() + binder, err := b.AddMessageFlags(&ctx, cmd.Flags(), inputType, options) if err != nil { return nil, err } - cmd.Args = binder.CobraArgs cmd.RunE = func(cmd *cobra.Command, args []string) error { + ctx = cmd.Context() + input, err := binder.BuildMessage(args) if err != nil { return err @@ -77,6 +81,7 @@ func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescrip // the client context uses the from flag to determine the signer. // this sets the signer flags to the from flag value if a custom signer flag is set. +<<<<<<< HEAD if binder.SignerInfo.FieldName != flags.FlagFrom { signer, err := cmd.Flags().GetString(binder.SignerInfo.FieldName) if err != nil { @@ -84,6 +89,15 @@ func (b *Builder) buildMethodCommandCommon(descriptor protoreflect.MethodDescrip } if err := cmd.Flags().Set(flags.FlagFrom, signer); err != nil { +======= + // marks the custom flag as required. + if binder.SignerInfo.FlagName != flags.FlagFrom { + if err := cmd.MarkFlagRequired(binder.SignerInfo.FlagName); err != nil { + return err + } + + if err := cmd.Flags().Set(flags.FlagFrom, cmd.Flag(binder.SignerInfo.FlagName).Value.String()); err != nil { +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) return err } } diff --git a/client/v2/autocli/common_test.go b/client/v2/autocli/common_test.go index 31dc43d3af62..eb2444e4dc1a 100644 --- a/client/v2/autocli/common_test.go +++ b/client/v2/autocli/common_test.go @@ -55,9 +55,6 @@ func initFixture(t *testing.T) *fixture { kr, err := sdkkeyring.New(sdk.KeyringServiceName(), sdkkeyring.BackendMemory, home, nil, encodingConfig.Codec) assert.NilError(t, err) - akr, err := sdkkeyring.NewAutoCLIKeyring(kr) - assert.NilError(t, err) - interfaceRegistry := encodingConfig.Codec.InterfaceRegistry() banktypes.RegisterInterfaces(interfaceRegistry) @@ -76,10 +73,16 @@ func initFixture(t *testing.T) *fixture { Builder: flag.Builder{ TypeResolver: protoregistry.GlobalTypes, FileResolver: protoregistry.GlobalFiles, +<<<<<<< HEAD Keyring: akr, AddressCodec: addresscodec.NewBech32Codec("cosmos"), ValidatorAddressCodec: addresscodec.NewBech32Codec("cosmosvaloper"), ConsensusAddressCodec: addresscodec.NewBech32Codec("cosmosvalcons"), +======= + AddressCodec: clientCtx.AddressCodec, + ValidatorAddressCodec: clientCtx.ValidatorAddressCodec, + ConsensusAddressCodec: clientCtx.ConsensusAddressCodec, +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) }, GetClientConn: func(*cobra.Command) (grpc.ClientConnInterface, error) { return conn, nil diff --git a/client/v2/autocli/flag/address.go b/client/v2/autocli/flag/address.go index 99d1a9c3284b..58108d094990 100644 --- a/client/v2/autocli/flag/address.go +++ b/client/v2/autocli/flag/address.go @@ -9,16 +9,18 @@ import ( "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/core/address" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdkkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) type addressStringType struct{} -func (a addressStringType) NewValue(_ context.Context, b *Builder) Value { - return &addressValue{addressCodec: b.AddressCodec, keyring: b.Keyring} +func (a addressStringType) NewValue(ctx *context.Context, b *Builder) Value { + return &addressValue{addressCodec: b.AddressCodec, ctx: ctx} } func (a addressStringType) DefaultValue() string { @@ -27,8 +29,8 @@ func (a addressStringType) DefaultValue() string { type validatorAddressStringType struct{} -func (a validatorAddressStringType) NewValue(_ context.Context, b *Builder) Value { - return &addressValue{addressCodec: b.ValidatorAddressCodec, keyring: b.Keyring} +func (a validatorAddressStringType) NewValue(ctx *context.Context, b *Builder) Value { + return &addressValue{addressCodec: b.ValidatorAddressCodec, ctx: ctx} } func (a validatorAddressStringType) DefaultValue() string { @@ -36,9 +38,10 @@ func (a validatorAddressStringType) DefaultValue() string { } type addressValue struct { - value string + ctx *context.Context addressCodec address.Codec - keyring keyring.Keyring + + value string } func (a addressValue) Get(protoreflect.Value) (protoreflect.Value, error) { @@ -51,7 +54,9 @@ func (a addressValue) String() string { // Set implements the flag.Value interface for addressValue. func (a *addressValue) Set(s string) error { - addr, err := a.keyring.LookupAddressByKeyName(s) + // we get the keyring on set, as in NewValue the context is the parent context (before RunE) + keyring := getKeyringFromCtx(a.ctx) + addr, err := keyring.LookupAddressByKeyName(s) if err == nil { addrStr, err := a.addressCodec.BytesToString(addr) if err != nil { @@ -62,9 +67,11 @@ func (a *addressValue) Set(s string) error { return nil } - // failed all validation, just accept the input. - // TODO(@julienrbrt), for final client/v2 2.0.0 revert the logic and - // do a better keyring instantiation. + _, err = a.addressCodec.StringToBytes(s) + if err != nil { + return fmt.Errorf("invalid account address or key name: %w", err) + } + a.value = s return nil @@ -76,11 +83,11 @@ func (a addressValue) Type() string { type consensusAddressStringType struct{} -func (a consensusAddressStringType) NewValue(ctx context.Context, b *Builder) Value { +func (a consensusAddressStringType) NewValue(ctx *context.Context, b *Builder) Value { return &consensusAddressValue{ addressValue: addressValue{ addressCodec: b.ConsensusAddressCodec, - keyring: b.Keyring, + ctx: ctx, }, } } @@ -102,7 +109,9 @@ func (a consensusAddressValue) String() string { } func (a *consensusAddressValue) Set(s string) error { - addr, err := a.keyring.LookupAddressByKeyName(s) + // we get the keyring on set, as in NewValue the context is the parent context (before RunE) + keyring := getKeyringFromCtx(a.ctx) + addr, err := keyring.LookupAddressByKeyName(s) if err == nil { addrStr, err := a.addressCodec.BytesToString(addr) if err != nil { @@ -127,11 +136,7 @@ func (a *consensusAddressValue) Set(s string) error { var pk cryptotypes.PubKey err2 := cdc.UnmarshalInterfaceJSON([]byte(s), &pk) if err2 != nil { - // failed all validation, just accept the input. - // TODO(@julienrbrt), for final client/v2 2.0.0 revert the logic and - // do a better keyring instantiation. - a.value = s - return nil + return fmt.Errorf("input isn't a pubkey (%w) or is an invalid account address (%w)", err, err2) } a.value, err = a.addressCodec.BytesToString(pk.Address()) @@ -141,3 +146,21 @@ func (a *consensusAddressValue) Set(s string) error { return nil } + +func getKeyringFromCtx(ctx *context.Context) keyring.Keyring { + dctx := *ctx + if dctx != nil { + if clientCtx := dctx.Value(client.ClientContextKey); clientCtx != nil { + k, err := sdkkeyring.NewAutoCLIKeyring(clientCtx.(*client.Context).Keyring) + if err != nil { + panic(fmt.Errorf("failed to create keyring: %w", err)) + } + + return k + } else if k := dctx.Value(keyring.KeyringContextKey); k != nil { + return k.(*keyring.KeyringImpl) + } + } + + return keyring.NoKeyring{} +} diff --git a/client/v2/autocli/flag/binary.go b/client/v2/autocli/flag/binary.go index a1a93179f290..7bd1c23293f7 100644 --- a/client/v2/autocli/flag/binary.go +++ b/client/v2/autocli/flag/binary.go @@ -14,7 +14,7 @@ type binaryType struct{} var _ Value = (*fileBinaryValue)(nil) -func (f binaryType) NewValue(context.Context, *Builder) Value { +func (f binaryType) NewValue(*context.Context, *Builder) Value { return &fileBinaryValue{} } diff --git a/client/v2/autocli/flag/builder.go b/client/v2/autocli/flag/builder.go index d797029fa2b9..019ef44e9d1e 100644 --- a/client/v2/autocli/flag/builder.go +++ b/client/v2/autocli/flag/builder.go @@ -16,7 +16,6 @@ import ( autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" msgv1 "cosmossdk.io/api/cosmos/msg/v1" - "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/client/v2/internal/flags" "cosmossdk.io/client/v2/internal/util" "cosmossdk.io/core/address" @@ -49,9 +48,6 @@ type Builder struct { messageFlagTypes map[protoreflect.FullName]Type scalarFlagTypes map[string]Type - // Keyring is the keyring to use for client/v2. - Keyring keyring.Keyring - // Address Codecs are the address codecs to use for client/v2. AddressCodec address.Codec ValidatorAddressCodec runtime.ValidatorAddressCodec @@ -90,10 +86,6 @@ func (b *Builder) ValidateAndComplete() error { return errors.New("consensus address codec is required in flag builder") } - if b.Keyring == nil { - b.Keyring = keyring.NoKeyring{} - } - if b.TypeResolver == nil { return errors.New("type resolver is required in flag builder") } @@ -118,12 +110,12 @@ func (b *Builder) DefineScalarFlagType(scalarName string, flagType Type) { } // AddMessageFlags adds flags for each field in the message to the flag set. -func (b *Builder) AddMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, messageType protoreflect.MessageType, commandOptions *autocliv1.RpcCommandOptions) (*MessageBinder, error) { +func (b *Builder) AddMessageFlags(ctx *context.Context, flagSet *pflag.FlagSet, messageType protoreflect.MessageType, commandOptions *autocliv1.RpcCommandOptions) (*MessageBinder, error) { return b.addMessageFlags(ctx, flagSet, messageType, commandOptions, namingOptions{}) } // addMessageFlags adds flags for each field in the message to the flag set. -func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, messageType protoreflect.MessageType, commandOptions *autocliv1.RpcCommandOptions, options namingOptions) (*MessageBinder, error) { +func (b *Builder) addMessageFlags(ctx *context.Context, flagSet *pflag.FlagSet, messageType protoreflect.MessageType, commandOptions *autocliv1.RpcCommandOptions, options namingOptions) (*MessageBinder, error) { messageBinder := &MessageBinder{ messageType: messageType, // positional args are also parsed using a FlagSet so that we can reuse all the same parsers @@ -135,7 +127,7 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m isPositional := map[string]bool{} - lengthPositionalArgsOptions := len(commandOptions.PositionalArgs) + positionalArgsLen := len(commandOptions.PositionalArgs) for i, arg := range commandOptions.PositionalArgs { isPositional[arg.ProtoField] = true @@ -147,17 +139,12 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m } } - field := fields.ByName(protoreflect.Name(arg.ProtoField)) - if field == nil { - return nil, fmt.Errorf("can't find field %s on %s", arg.ProtoField, messageType.Descriptor().FullName()) - } - if arg.Optional && arg.Varargs { return nil, fmt.Errorf("positional argument %s can't be both optional and varargs", arg.ProtoField) } if arg.Varargs { - if i != lengthPositionalArgsOptions-1 { + if i != positionalArgsLen-1 { return nil, fmt.Errorf("varargs positional argument %s must be the last argument", arg.ProtoField) } @@ -165,13 +152,18 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m } if arg.Optional { - if i != lengthPositionalArgsOptions-1 { + if i != positionalArgsLen-1 { return nil, fmt.Errorf("optional positional argument %s must be the last argument", arg.ProtoField) } messageBinder.hasOptional = true } + field := fields.ByName(protoreflect.Name(arg.ProtoField)) + if field == nil { + return nil, fmt.Errorf("can't find field %s on %s", arg.ProtoField, messageType.Descriptor().FullName()) + } + _, hasValue, err := b.addFieldFlag( ctx, messageBinder.positionalFlagSet, @@ -189,19 +181,20 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m }) } - if messageBinder.hasVarargs { - messageBinder.CobraArgs = cobra.MinimumNArgs(lengthPositionalArgsOptions - 1) - messageBinder.mandatoryArgUntil = lengthPositionalArgsOptions - 1 - } else if messageBinder.hasOptional { - messageBinder.CobraArgs = cobra.RangeArgs(lengthPositionalArgsOptions-1, lengthPositionalArgsOptions) - messageBinder.mandatoryArgUntil = lengthPositionalArgsOptions - 1 - } else { - messageBinder.CobraArgs = cobra.ExactArgs(lengthPositionalArgsOptions) - messageBinder.mandatoryArgUntil = lengthPositionalArgsOptions + switch { + case messageBinder.hasVarargs: + messageBinder.CobraArgs = cobra.MinimumNArgs(positionalArgsLen - 1) + messageBinder.mandatoryArgUntil = positionalArgsLen - 1 + case messageBinder.hasOptional: + messageBinder.CobraArgs = cobra.RangeArgs(positionalArgsLen-1, positionalArgsLen) + messageBinder.mandatoryArgUntil = positionalArgsLen - 1 + default: + messageBinder.CobraArgs = cobra.ExactArgs(positionalArgsLen) + messageBinder.mandatoryArgUntil = positionalArgsLen } // validate flag options - for name := range commandOptions.FlagOptions { + for name, opts := range commandOptions.FlagOptions { if fields.ByName(protoreflect.Name(name)) == nil { return nil, fmt.Errorf("can't find field %s on %s specified as a flag", name, messageType.Descriptor().FullName()) } @@ -210,14 +203,15 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m if name == signerFieldName { messageBinder.SignerInfo = SignerInfo{ FieldName: name, - IsFlag: false, + IsFlag: true, + FlagName: opts.Name, } } } // if signer has not been specified as positional arguments, // add it as `--from` flag (instead of --field-name flags) - if signerFieldName != "" && messageBinder.SignerInfo.FieldName == "" { + if signerFieldName != "" && messageBinder.SignerInfo == (SignerInfo{}) { if commandOptions.FlagOptions == nil { commandOptions.FlagOptions = make(map[string]*autocliv1.FlagOptions) } @@ -229,8 +223,9 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m } messageBinder.SignerInfo = SignerInfo{ - FieldName: flags.FlagFrom, + FieldName: signerFieldName, IsFlag: true, + FlagName: flags.FlagFrom, } } @@ -238,13 +233,15 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m flagOptsByFlagName := map[string]*autocliv1.FlagOptions{} for i := 0; i < fields.Len(); i++ { field := fields.Get(i) + fieldName := string(field.Name()) + // skips positional args and signer field if already set - if isPositional[string(field.Name())] || - (string(field.Name()) == signerFieldName && messageBinder.SignerInfo.FieldName == flags.FlagFrom) { + if isPositional[fieldName] || + (fieldName == signerFieldName && messageBinder.SignerInfo.FlagName == flags.FlagFrom) { continue } - flagOpts := commandOptions.FlagOptions[string(field.Name())] + flagOpts := commandOptions.FlagOptions[fieldName] name, hasValue, err := b.addFieldFlag(ctx, flagSet, field, flagOpts, options) flagOptsByFlagName[name] = flagOpts if err != nil { @@ -274,7 +271,7 @@ func (b *Builder) addMessageFlags(ctx context.Context, flagSet *pflag.FlagSet, m } // bindPageRequest create a flag for pagination -func (b *Builder) bindPageRequest(ctx context.Context, flagSet *pflag.FlagSet, field protoreflect.FieldDescriptor) (HasValue, error) { +func (b *Builder) bindPageRequest(ctx *context.Context, flagSet *pflag.FlagSet, field protoreflect.FieldDescriptor) (HasValue, error) { return b.addMessageFlags( ctx, flagSet, @@ -291,7 +288,7 @@ type namingOptions struct { } // addFieldFlag adds a flag for the provided field to the flag set. -func (b *Builder) addFieldFlag(ctx context.Context, flagSet *pflag.FlagSet, field protoreflect.FieldDescriptor, opts *autocliv1.FlagOptions, options namingOptions) (name string, hasValue HasValue, err error) { +func (b *Builder) addFieldFlag(ctx *context.Context, flagSet *pflag.FlagSet, field protoreflect.FieldDescriptor, opts *autocliv1.FlagOptions, options namingOptions) (name string, hasValue HasValue, err error) { if opts == nil { opts = &autocliv1.FlagOptions{} } diff --git a/client/v2/autocli/flag/coin.go b/client/v2/autocli/flag/coin.go index 3cf5afc5818b..57c0e85983e4 100644 --- a/client/v2/autocli/flag/coin.go +++ b/client/v2/autocli/flag/coin.go @@ -17,7 +17,7 @@ type coinValue struct { value *basev1beta1.Coin } -func (c coinType) NewValue(context.Context, *Builder) Value { +func (c coinType) NewValue(*context.Context, *Builder) Value { return &coinValue{} } diff --git a/client/v2/autocli/flag/duration.go b/client/v2/autocli/flag/duration.go index cf9fe67af495..df02cba700f7 100644 --- a/client/v2/autocli/flag/duration.go +++ b/client/v2/autocli/flag/duration.go @@ -10,7 +10,7 @@ import ( type durationType struct{} -func (d durationType) NewValue(context.Context, *Builder) Value { +func (d durationType) NewValue(*context.Context, *Builder) Value { return &durationValue{} } diff --git a/client/v2/autocli/flag/enum.go b/client/v2/autocli/flag/enum.go index 8b555d9232ec..72b27528a9a5 100644 --- a/client/v2/autocli/flag/enum.go +++ b/client/v2/autocli/flag/enum.go @@ -14,7 +14,7 @@ type enumType struct { enum protoreflect.EnumDescriptor } -func (b enumType) NewValue(context.Context, *Builder) Value { +func (b enumType) NewValue(*context.Context, *Builder) Value { val := &enumValue{ enum: b.enum, valMap: map[string]protoreflect.EnumValueDescriptor{}, diff --git a/client/v2/autocli/flag/interface.go b/client/v2/autocli/flag/interface.go index c7bb61b9d148..1d983c09c630 100644 --- a/client/v2/autocli/flag/interface.go +++ b/client/v2/autocli/flag/interface.go @@ -9,9 +9,8 @@ import ( // Type specifies a custom flag type. type Type interface { - // NewValue returns a new pflag.Value which must also implement either - // SimpleValue or ListValue. - NewValue(context.Context, *Builder) Value + // NewValue returns a new pflag.Value which must also implement either SimpleValue or ListValue. + NewValue(*context.Context, *Builder) Value // DefaultValue is the default value for this type. DefaultValue() string diff --git a/client/v2/autocli/flag/json_message.go b/client/v2/autocli/flag/json_message.go index 4e93792aaf23..437388994ee9 100644 --- a/client/v2/autocli/flag/json_message.go +++ b/client/v2/autocli/flag/json_message.go @@ -20,7 +20,7 @@ type jsonMessageFlagType struct { messageDesc protoreflect.MessageDescriptor } -func (j jsonMessageFlagType) NewValue(_ context.Context, builder *Builder) Value { +func (j jsonMessageFlagType) NewValue(_ *context.Context, builder *Builder) Value { return &jsonMessageFlagValue{ messageType: util.ResolveMessageType(builder.TypeResolver, j.messageDesc), jsonMarshalOptions: protojson.MarshalOptions{Resolver: builder.TypeResolver}, diff --git a/client/v2/autocli/flag/list.go b/client/v2/autocli/flag/list.go index 72f84c8be3ee..d30f2992ebd9 100644 --- a/client/v2/autocli/flag/list.go +++ b/client/v2/autocli/flag/list.go @@ -52,7 +52,7 @@ type compositeListType struct { simpleType Type } -func (t compositeListType) NewValue(ctx context.Context, opts *Builder) Value { +func (t compositeListType) NewValue(ctx *context.Context, opts *Builder) Value { return &compositeListValue{ simpleType: t.simpleType, values: nil, @@ -68,7 +68,7 @@ func (t compositeListType) DefaultValue() string { type compositeListValue struct { simpleType Type values []protoreflect.Value - ctx context.Context + ctx *context.Context opts *Builder } diff --git a/client/v2/autocli/flag/map.go b/client/v2/autocli/flag/map.go index c2f512503dfa..a5d70c7f7fce 100644 --- a/client/v2/autocli/flag/map.go +++ b/client/v2/autocli/flag/map.go @@ -180,7 +180,7 @@ type compositeMapValue[T comparable] struct { keyType string valueType Type values map[T]protoreflect.Value - ctx context.Context + ctx *context.Context opts *Builder } @@ -188,7 +188,7 @@ func (m compositeMapType[T]) DefaultValue() string { return "" } -func (m compositeMapType[T]) NewValue(ctx context.Context, opts *Builder) Value { +func (m compositeMapType[T]) NewValue(ctx *context.Context, opts *Builder) Value { return &compositeMapValue[T]{ keyValueResolver: m.keyValueResolver, valueType: m.valueType, diff --git a/client/v2/autocli/flag/messager_binder.go b/client/v2/autocli/flag/messager_binder.go index ecd822395030..46a28baa285a 100644 --- a/client/v2/autocli/flag/messager_binder.go +++ b/client/v2/autocli/flag/messager_binder.go @@ -15,7 +15,9 @@ import ( type SignerInfo struct { PositionalArgIndex int IsFlag bool - FieldName string + + FieldName string + FlagName string // flag name (always set if IsFlag is true) } // MessageBinder binds multiple flags in a flag set to a protobuf message. @@ -25,12 +27,12 @@ type MessageBinder struct { positionalFlagSet *pflag.FlagSet positionalArgs []fieldBinding + flagBindings []fieldBinding + messageType protoreflect.MessageType + hasVarargs bool hasOptional bool mandatoryArgUntil int - - flagBindings []fieldBinding - messageType protoreflect.MessageType } // BuildMessage builds and returns a new message for the bound flags. diff --git a/client/v2/autocli/flag/pubkey.go b/client/v2/autocli/flag/pubkey.go new file mode 100644 index 000000000000..adc07956f690 --- /dev/null +++ b/client/v2/autocli/flag/pubkey.go @@ -0,0 +1,60 @@ +package flag + +import ( + "context" + "fmt" + + "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +type pubkeyType struct{} + +func (a pubkeyType) NewValue(_ *context.Context, _ *Builder) Value { + return &pubkeyValue{} +} + +func (a pubkeyType) DefaultValue() string { + return "" +} + +type pubkeyValue struct { + value *types.Any +} + +func (a pubkeyValue) Get(protoreflect.Value) (protoreflect.Value, error) { + return protoreflect.ValueOf(a.value), nil +} + +func (a pubkeyValue) String() string { + return a.value.String() +} + +func (a *pubkeyValue) Set(s string) error { + registry := types.NewInterfaceRegistry() + cryptocodec.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) + + var pk cryptotypes.PubKey + err := cdc.UnmarshalInterfaceJSON([]byte(s), &pk) + if err != nil { + return fmt.Errorf("input isn't a pubkey: %w", err) + } + + any, err := types.NewAnyWithValue(pk) + if err != nil { + return fmt.Errorf("error converting to any type") + } + + a.value = any + + return nil +} + +func (a pubkeyValue) Type() string { + return "pubkey" +} diff --git a/client/v2/autocli/flag/timestamp.go b/client/v2/autocli/flag/timestamp.go index c759c70f2178..d77f0293bd97 100644 --- a/client/v2/autocli/flag/timestamp.go +++ b/client/v2/autocli/flag/timestamp.go @@ -10,7 +10,7 @@ import ( type timestampType struct{} -func (t timestampType) NewValue(context.Context, *Builder) Value { +func (t timestampType) NewValue(*context.Context, *Builder) Value { return ×tampValue{} } diff --git a/client/v2/autocli/keyring/keyring.go b/client/v2/autocli/keyring/keyring.go new file mode 100644 index 000000000000..a838b12d8455 --- /dev/null +++ b/client/v2/autocli/keyring/keyring.go @@ -0,0 +1,48 @@ +package keyring + +import ( + "context" + + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + + "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// KeyringContextKey is the key used to store the keyring in the context. +// The keyring must be wrapped using the KeyringImpl. +var KeyringContextKey struct{} + +var _ Keyring = &KeyringImpl{} + +type KeyringImpl struct { + k Keyring +} + +// NewKeyringInContext returns a new context with the keyring set. +func NewKeyringInContext(ctx context.Context, k Keyring) context.Context { + return context.WithValue(ctx, KeyringContextKey, NewKeyringImpl(k)) +} + +func NewKeyringImpl(k Keyring) *KeyringImpl { + return &KeyringImpl{k: k} +} + +// GetPubKey implements Keyring. +func (k *KeyringImpl) GetPubKey(name string) (types.PubKey, error) { + return k.k.GetPubKey(name) +} + +// List implements Keyring. +func (k *KeyringImpl) List() ([]string, error) { + return k.k.List() +} + +// LookupAddressByKeyName implements Keyring. +func (k *KeyringImpl) LookupAddressByKeyName(name string) ([]byte, error) { + return k.k.LookupAddressByKeyName(name) +} + +// Sign implements Keyring. +func (k *KeyringImpl) Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) { + return k.k.Sign(name, msg, signMode) +} diff --git a/client/v2/autocli/msg.go b/client/v2/autocli/msg.go index 5bcbd9baa33a..e95c2116fdb0 100644 --- a/client/v2/autocli/msg.go +++ b/client/v2/autocli/msg.go @@ -102,9 +102,7 @@ func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autoc continue } - if methodCmd != nil { - cmd.AddCommand(methodCmd) - } + cmd.AddCommand(methodCmd) } return nil @@ -121,6 +119,18 @@ func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor clientCtx = clientCtx.WithCmdContext(cmd.Context()) clientCtx = clientCtx.WithOutput(cmd.OutOrStdout()) +<<<<<<< HEAD +======= + fd := input.Descriptor().Fields().ByName(protoreflect.Name(flag.GetSignerFieldName(input.Descriptor()))) + addressCodec := b.Builder.AddressCodec + + // handle gov proposals commands + skipProposal, _ := cmd.Flags().GetBool(flags.FlagNoProposal) + if options.GovProposal && !skipProposal { + return b.handleGovProposal(cmd, input, clientCtx, addressCodec, fd) + } + +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) // set signer to signer field if empty fd := input.Descriptor().Fields().ByName(protoreflect.Name(flag.GetSignerFieldName(input.Descriptor()))) if addr := input.Get(fd).String(); addr == "" { @@ -160,9 +170,56 @@ func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor } // silence usage only for inner txs & queries commands - if cmd != nil { - cmd.SilenceUsage = true - } + cmd.SilenceUsage = true +<<<<<<< HEAD return cmd, err +======= + // set gov proposal flags if command is a gov proposal + if options.GovProposal { + govcli.AddGovPropFlagsToCmd(cmd) + cmd.Flags().Bool(flags.FlagNoProposal, false, "Skip gov proposal and submit a normal transaction") + } + + return cmd, nil +} + +// handleGovProposal sets the authority field of the message to the gov module address and creates a gov proposal. +func (b *Builder) handleGovProposal( + cmd *cobra.Command, + input protoreflect.Message, + clientCtx client.Context, + addressCodec addresscodec.Codec, + fd protoreflect.FieldDescriptor, +) error { + govAuthority := authtypes.NewModuleAddress(govtypes.ModuleName) + authority, err := addressCodec.BytesToString(govAuthority.Bytes()) + if err != nil { + return fmt.Errorf("failed to convert gov authority: %w", err) + } + input.Set(fd, protoreflect.ValueOfString(authority)) + + signerFromFlag := clientCtx.GetFromAddress() + signer, err := addressCodec.BytesToString(signerFromFlag.Bytes()) + if err != nil { + return fmt.Errorf("failed to set signer on message, got %q: %w", signerFromFlag, err) + } + + proposal, err := govcli.ReadGovPropCmdFlags(signer, cmd.Flags()) + if err != nil { + return err + } + + // AutoCLI uses protov2 messages, while the SDK only supports proto v1 messages. + // Here we use dynamicpb, to create a proto v1 compatible message. + // The SDK codec will handle protov2 -> protov1 (marshal) + msg := dynamicpb.NewMessage(input.Descriptor()) + proto.Merge(msg, input.Interface()) + + if err := proposal.SetMsgs([]gogoproto.Message{msg}); err != nil { + return fmt.Errorf("failed to set msg in proposal %w", err) + } + + return clienttx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposal) +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) } diff --git a/client/v2/autocli/msg_test.go b/client/v2/autocli/msg_test.go index 5937ba24dbc7..c24b761419e8 100644 --- a/client/v2/autocli/msg_test.go +++ b/client/v2/autocli/msg_test.go @@ -55,6 +55,25 @@ func TestMsg(t *testing.T) { assert.NilError(t, err) golden.Assert(t, out.String(), "msg-output.golden") + out, err = runCmd(fixture, buildCustomModuleMsgCommand(&autocliv1.ServiceCommandDescriptor{ + Service: bankv1beta1.Msg_ServiceDesc.ServiceName, + RpcCommandOptions: []*autocliv1.RpcCommandOptions{ + { + RpcMethod: "Send", + Use: "send [from_key_or_address] [to_address] [amount] [flags]", + Short: "Send coins from one account to another", + PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "from_address"}, {ProtoField: "to_address"}, {ProtoField: "amount"}}, + }, + }, + EnhanceCustomCommand: true, + }), "send", + "cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk", "cosmos1y74p8wyy4enfhfn342njve6cjmj5c8dtl6emdk", "1foo", + "--generate-only", + "--output", "json", + ) + assert.NilError(t, err) + assertNormalizedJSONEqual(t, out.Bytes(), goldenLoad(t, "msg-output.golden")) + out, err = runCmd(fixture, buildCustomModuleMsgCommand(&autocliv1.ServiceCommandDescriptor{ Service: bankv1beta1.Msg_ServiceDesc.ServiceName, RpcCommandOptions: []*autocliv1.RpcCommandOptions{ diff --git a/client/v2/autocli/query_test.go b/client/v2/autocli/query_test.go index 07d6354928e0..c949ea021a0a 100644 --- a/client/v2/autocli/query_test.go +++ b/client/v2/autocli/query_test.go @@ -684,7 +684,7 @@ func TestNotFoundErrorsQuery(t *testing.T) { b.AddQueryConnFlags = nil b.AddTxConnFlags = nil - buildModuleQueryCommand := func(moduleName string, cmdDescriptor *autocliv1.ServiceCommandDescriptor) (*cobra.Command, error) { + buildModuleQueryCommand := func(_ string, cmdDescriptor *autocliv1.ServiceCommandDescriptor) (*cobra.Command, error) { cmd := topLevelCmd(context.Background(), "query", "Querying subcommands") err := b.AddMsgServiceCommands(cmd, cmdDescriptor) return cmd, err diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index f56b849a9c56..43145514619b 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -13,7 +13,11 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" +<<<<<<< HEAD "github.com/cosmos/cosmos-sdk/crypto/keyring" +======= + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) "github.com/cosmos/cosmos-sdk/server" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -100,8 +104,11 @@ func NewRootCmd() *cobra.Command { // add keyring to autocli opts autoCliOpts := tempApp.AutoCliOpts() +<<<<<<< HEAD initClientCtx, _ = config.ReadDefaultValuesFromDefaultClientConfig(initClientCtx) autoCliOpts.Keyring, _ = keyring.NewAutoCLIKeyring(initClientCtx.Keyring) +======= +>>>>>>> ca195c152 (feat(client/v2): get keyring from context (#19646)) autoCliOpts.ClientCtx = initClientCtx if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { diff --git a/simapp/simd/cmd/root_v2.go b/simapp/simd/cmd/root_v2.go index 94ef4cd9d17b..3661250b89e4 100644 --- a/simapp/simd/cmd/root_v2.go +++ b/simapp/simd/cmd/root_v2.go @@ -8,7 +8,6 @@ import ( "github.com/spf13/cobra" "cosmossdk.io/client/v2/autocli" - clientv2keyring "cosmossdk.io/client/v2/autocli/keyring" "cosmossdk.io/core/address" "cosmossdk.io/depinject" "cosmossdk.io/log" @@ -18,7 +17,6 @@ import ( "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/server" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" "github.com/cosmos/cosmos-sdk/types/module" @@ -43,7 +41,6 @@ func NewRootCmd() *cobra.Command { ), depinject.Provide( ProvideClientContext, - ProvideKeyring, ), ), &autoCliOpts, @@ -121,12 +118,3 @@ func ProvideClientContext( return clientCtx } - -func ProvideKeyring(clientCtx client.Context, addressCodec address.Codec) (clientv2keyring.Keyring, error) { - kb, err := client.NewKeyringFromBackend(clientCtx, clientCtx.Keyring.Backend()) - if err != nil { - return nil, err - } - - return keyring.NewAutoCLIKeyring(kb) -}