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

refactor(client/v2): use tx factoryV2 #23064

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion client/v2/autocli/keyring/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ type Keyring interface {
KeyType(name string) (uint, error)

// KeyInfo given a key name or address returns key name, key address and key type.
KeyInfo(nameOrAddr string) (string, string, uint, error)
KeyInfo(nameOrAddr string) (string, string, []byte, uint, error)
}
2 changes: 1 addition & 1 deletion client/v2/autocli/keyring/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,6 @@ func (k *KeyringImpl) KeyType(name string) (uint, error) {
}

// KeyInfo given a key name or address returns key name, key address and key type.
func (k *KeyringImpl) KeyInfo(nameOrAddr string) (string, string, uint, error) {
func (k *KeyringImpl) KeyInfo(nameOrAddr string) (string, string, []byte, uint, error) {
return k.k.KeyInfo(nameOrAddr)
}
4 changes: 2 additions & 2 deletions client/v2/autocli/keyring/no_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ func (k NoKeyring) KeyType(name string) (uint, error) {
return 0, errNoKeyring
}

func (k NoKeyring) KeyInfo(name string) (string, string, uint, error) {
return "", "", 0, errNoKeyring
func (k NoKeyring) KeyInfo(name string) (string, string, []byte, uint, error) {
return "", "", nil, 0, errNoKeyring
}
70 changes: 43 additions & 27 deletions client/v2/autocli/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
"cosmossdk.io/client/v2/autocli/flag"
"cosmossdk.io/client/v2/autocli/prompt"
v2context "cosmossdk.io/client/v2/context"
"cosmossdk.io/client/v2/internal/flags"
"cosmossdk.io/client/v2/internal/governance"
"cosmossdk.io/client/v2/internal/print"
Expand All @@ -21,10 +23,6 @@
"cosmossdk.io/core/transaction"

authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/input"
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
)

// BuildMsgCommand builds the msg commands for all the provided modules. If a custom command is provided for a
Expand Down Expand Up @@ -122,21 +120,17 @@
// BuildMsgMethodCommand returns a command that outputs the JSON representation of the message.
func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor, options *autocliv1.RpcCommandOptions) (*cobra.Command, error) {
execFunc := func(cmd *cobra.Command, input protoreflect.Message) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

clientCtx = clientCtx.WithCmdContext(cmd.Context())
clientCtx = clientCtx.WithOutput(cmd.OutOrStdout())

fd := input.Descriptor().Fields().ByName(protoreflect.Name(flag.GetSignerFieldName(input.Descriptor())))
addressCodec := b.Builder.AddressCodec

ctx, err := b.getContext(cmd)
if err != nil {
return err
}
// handle gov proposals commands
skipProposal, _ := cmd.Flags().GetBool(flags.FlagNoProposal)
if options.GovProposal && !skipProposal {
return b.handleGovProposal(cmd, input, clientCtx, addressCodec, fd)
return b.handleGovProposal(ctx, cmd, input, addressCodec, fd)
}

// set signer to signer field if empty
Expand All @@ -152,8 +146,8 @@
}
}

signerFromFlag := clientCtx.GetFromAddress()
signer, err := addressCodec.BytesToString(signerFromFlag.Bytes())
_, signerFromFlag, err := b.getFromAddress(ctx, cmd)
Fixed Show fixed Hide fixed
signer, err := addressCodec.BytesToString(signerFromFlag)
if err != nil {
return fmt.Errorf("failed to set signer on message, got %v: %w", signerFromFlag, err)
}
Expand All @@ -167,7 +161,7 @@
msg := dynamicpb.NewMessage(input.Descriptor())
proto.Merge(msg, input.Interface())

return clienttx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
return b.generateOrBroadcastTxWithV2(cmd, msg)
}

cmd, err := b.buildMethodCommandCommon(descriptor, options, execFunc)
Expand All @@ -193,9 +187,9 @@

// handleGovProposal sets the authority field of the message to the gov module address and creates a gov proposal.
func (b *Builder) handleGovProposal(
ctx context.Context,
cmd *cobra.Command,
input protoreflect.Message,
clientCtx client.Context,
addressCodec addresscodec.Codec,
fd protoreflect.FieldDescriptor,
) error {
Expand All @@ -206,12 +200,10 @@
}
input.Set(fd, protoreflect.ValueOfString(authority))

signerFromFlag := clientCtx.GetFromAddress()
signer, err := addressCodec.BytesToString(signerFromFlag.Bytes())
signer, _, err := b.getFromAddress(ctx, cmd)
if err != nil {
return fmt.Errorf("failed to set signer on message, got %q: %w", signerFromFlag, err)
return err
}

proposal, err := governance.ReadGovPropCmdFlags(signer, cmd.Flags())
if err != nil {
return err
Expand All @@ -227,12 +219,32 @@
return fmt.Errorf("failed to set msg in proposal %w", err)
}

return clienttx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposal)
return b.generateOrBroadcastTxWithV2(cmd, proposal)
}

// getFromAddress retrieves the sender's address from the command flags and keyring.
// It returns the address string, raw address bytes, and any error encountered.
// The address is obtained from the --from flag and looked up in the keyring.
func (b *Builder) getFromAddress(ctx context.Context, cmd *cobra.Command) (string, []byte, error) {
clientCtx, err := v2context.ClientContextFromGoContext(ctx)
if err != nil {
return "", nil, err
}

from, err := cmd.Flags().GetString(flags.FlagFrom)
if err != nil {
return "", nil, err
}

_, addrStr, addr, _, err := clientCtx.Keyring.KeyInfo(from)
if err != nil {
return "", nil, err
}

return addrStr, addr, nil
}

// generateOrBroadcastTxWithV2 generates or broadcasts a transaction with the provided messages using v2 transaction handling.
//
//nolint:unused // It'll be used once BuildMsgMethodCommand is updated to use factory v2.
func (b *Builder) generateOrBroadcastTxWithV2(cmd *cobra.Command, msgs ...transaction.Msg) error {
ctx, err := b.getContext(cmd)
if err != nil {
Expand Down Expand Up @@ -274,8 +286,6 @@

// userConfirmation returns a function that prompts the user for confirmation
// before signing and broadcasting a transaction.
//
//nolint:unused // It is used in generateOrBroadcastTxWithV2 however linting is complaining.
func (b *Builder) userConfirmation(cmd *cobra.Command) func([]byte) (bool, error) {
format, _ := cmd.Flags().GetString(flags.FlagOutput)
printer := print.Printer{
Expand All @@ -289,7 +299,13 @@
return false, err
}
buf := bufio.NewReader(cmd.InOrStdin())
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, cmd.ErrOrStderr())

err = printer.PrintString("confirm transaction before signing and broadcasting y/N: ")
if err != nil {
return false, err
}

ok, err := prompt.UserConfirmation(buf)
if err != nil {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "error: %v\ncanceled transaction\n", err)
return false, err
Expand Down
26 changes: 25 additions & 1 deletion client/v2/autocli/prompt/util.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package prompt

import (
"bufio"
"fmt"
"strings"

"github.com/manifoldco/promptui"
"google.golang.org/protobuf/reflect/protoreflect"
Expand All @@ -17,7 +19,7 @@ func Select(label string, options []string) (string, error) {

_, selectedProposalType, err := selectUi.Run()
if err != nil {
return "", fmt.Errorf("failed to prompt proposal types: %w", err)
return "", fmt.Errorf("failed to prompt select list: %w", err)
}

return selectedProposalType, nil
Expand Down Expand Up @@ -68,3 +70,25 @@ func SetDefaults(msg protoreflect.Message, defaults map[string]interface{}) {
}
}
}

// UserConfirmation prompts the user for a yes/no confirmation using the provided bufio.Reader.
// It reads a line of input, trims whitespace, and returns true if the first character is 'y' or 'Y'.
// Returns false for empty input or any other response. Returns an error if reading fails.
func UserConfirmation(r *bufio.Reader) (bool, error) {
response, err := r.ReadString('\n')
if err != nil {
return false, err
}

response = strings.TrimSpace(response)
if len(response) == 0 {
return false, nil
}

response = strings.ToLower(response)
if response[0] == 'y' && len(response) <= 3 {
return true, nil
}

return false, nil
}
13 changes: 11 additions & 2 deletions client/v2/internal/print/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func NewPrinter(cmd *cobra.Command) (*Printer, error) {

// PrintString prints the raw string
func (p *Printer) PrintString(str string) error {
return p.PrintBytes([]byte(str))
return p.print([]byte(str))
}

// PrintRaw prints raw JSON message without marshaling
Expand All @@ -63,12 +63,21 @@ func (p *Printer) PrintBytes(out []byte) error {
}
}

err = p.print(out)
if err != nil {
return err
}

return nil
}

func (p *Printer) print(out []byte) error {
writer := p.Output
if writer == nil {
writer = os.Stdout
}

_, err = writer.Write(out)
_, err := writer.Write(out)
if err != nil {
return err
}
Expand Down
5 changes: 1 addition & 4 deletions client/v2/tx/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,7 @@ func txParamsFromFlagSet(flags *pflag.FlagSet, keybase keyring2.Keyring, ac addr
if isDryRun || generateOnly {
addr, err = ac.StringToBytes(from)
} else {
fromName, fromAddress, _, err = keybase.KeyInfo(from)
if err == nil {
addr, err = ac.StringToBytes(fromAddress)
}
fromName, fromAddress, addr, _, err = keybase.KeyInfo(from)
}
if err != nil {
return params, err
Expand Down
20 changes: 12 additions & 8 deletions crypto/keyring/autocli.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type autoCLIKeyring interface {
KeyType(name string) (uint, error)

// KeyInfo given a key name or address returns key name, key address and key type.
KeyInfo(name string) (string, string, uint, error)
KeyInfo(name string) (string, string, []byte, uint, error)
}

// NewAutoCLIKeyring wraps the SDK keyring and makes it compatible with the AutoCLI keyring interfaces.
Expand Down Expand Up @@ -107,30 +107,34 @@ func (a *autoCLIKeyringAdapter) KeyType(name string) (uint, error) {
}

// KeyInfo returns key name, key address, and key type given a key name or address.
func (a *autoCLIKeyringAdapter) KeyInfo(nameOrAddr string) (string, string, uint, error) {
func (a *autoCLIKeyringAdapter) KeyInfo(nameOrAddr string) (string, string, []byte, uint, error) {
addr, err := a.ac.StringToBytes(nameOrAddr)
if err != nil {
// If conversion fails, it's likely a name, not an address
record, err := a.Keyring.Key(nameOrAddr)
if err != nil {
return "", "", 0, err
return "", "", nil, 0, err
}
addr, err = record.GetAddress()
if err != nil {
return "", "", 0, err
return "", "", nil, 0, err
}
addrStr, err := a.ac.BytesToString(addr)
if err != nil {
return "", "", 0, err
return "", "", nil, 0, err
}
return record.Name, addrStr, uint(record.GetType()), nil
return record.Name, addrStr, addr, uint(record.GetType()), nil
}

// If conversion succeeds, it's an address, get the key info by address
record, err := a.Keyring.KeyByAddress(addr)
if err != nil {
return "", "", 0, err
return "", "", nil, 0, err
}
addr, err = record.GetAddress()
if err != nil {
return "", "", nil, 0, err
}

return record.Name, nameOrAddr, uint(record.GetType()), nil
return record.Name, nameOrAddr, addr, uint(record.GetType()), nil
}
Loading