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
35 changes: 7 additions & 28 deletions client/v2/autocli/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,16 @@ import (
"cosmossdk.io/x/bank"
banktypes "cosmossdk.io/x/bank/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/codec/testutil"
sdkkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
)

type fixture struct {
conn *testClientConn
b *Builder
clientCtx client.Context
conn *testClientConn
b *Builder

home string
chainID string
Expand All @@ -57,33 +54,17 @@ func initFixture(t *testing.T) *fixture {
assert.NilError(t, err)

encodingConfig := moduletestutil.MakeTestEncodingConfig(testutil.CodecOptions{}, bank.AppModule{})
kr, err := sdkkeyring.New(sdk.KeyringServiceName(), sdkkeyring.BackendMemory, home, nil, encodingConfig.Codec)
assert.NilError(t, err)

interfaceRegistry := encodingConfig.Codec.InterfaceRegistry()
banktypes.RegisterInterfaces(interfaceRegistry)

clientCtx := client.Context{}.
WithAddressCodec(interfaceRegistry.SigningContext().AddressCodec()).
WithValidatorAddressCodec(interfaceRegistry.SigningContext().ValidatorAddressCodec()).
WithConsensusAddressCodec(addresscodec.NewBech32Codec("cosmosvalcons")).
WithKeyring(kr).
WithKeyringDir(home).
WithHomeDir(home).
WithViper("").
WithInterfaceRegistry(interfaceRegistry).
WithTxConfig(encodingConfig.TxConfig).
WithAccountRetriever(client.MockAccountRetriever{}).
WithChainID("autocli-test")

conn := &testClientConn{ClientConn: clientConn}
b := &Builder{
Builder: flag.Builder{
TypeResolver: protoregistry.GlobalTypes,
FileResolver: protoregistry.GlobalFiles,
AddressCodec: clientCtx.AddressCodec,
ValidatorAddressCodec: clientCtx.ValidatorAddressCodec,
ConsensusAddressCodec: clientCtx.ConsensusAddressCodec,
AddressCodec: interfaceRegistry.SigningContext().AddressCodec(),
ValidatorAddressCodec: interfaceRegistry.SigningContext().ValidatorAddressCodec(),
ConsensusAddressCodec: addresscodec.NewBech32Codec("cosmosvalcons"),
},
GetClientConn: func(*cobra.Command) (grpc.ClientConnInterface, error) {
return conn, nil
Expand All @@ -95,10 +76,8 @@ func initFixture(t *testing.T) *fixture {
assert.NilError(t, b.ValidateAndComplete())

return &fixture{
conn: conn,
b: b,
clientCtx: clientCtx,

conn: conn,
b: b,
home: home,
chainID: "autocli-test",
kBackend: sdkkeyring.BackendMemory,
Expand Down
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
}
79 changes: 51 additions & 28 deletions client/v2/autocli/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (

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 @@ import (
"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 @@ func (b *Builder) AddMsgServiceCommands(cmd *cobra.Command, cmdDescriptor *autoc
// 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,10 +146,9 @@ func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor
}
}

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

input.Set(fd, protoreflect.ValueOfString(signer))
Expand All @@ -167,7 +160,7 @@ func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor
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 +186,9 @@ func (b *Builder) BuildMsgMethodCommand(descriptor protoreflect.MethodDescriptor

// 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 +199,10 @@ func (b *Builder) handleGovProposal(
}
input.Set(fd, protoreflect.ValueOfString(authority))

signerFromFlag := clientCtx.GetFromAddress()
signer, err := addressCodec.BytesToString(signerFromFlag.Bytes())
signer, _, err := b.getFromAddress(ctx, cmd, addressCodec)
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 +218,37 @@ func (b *Builder) handleGovProposal(
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, ac addresscodec.Codec) (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
}

addr, err := ac.StringToBytes(from)
if err == nil {
return from, addr, nil
}

_, 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 @@ -264,6 +280,9 @@ func (b *Builder) generateOrBroadcastTxWithV2(cmd *cobra.Command, msgs ...transa
}

output, _ := cmd.Flags().GetString(flags.FlagOutput)
if genOnly {
output = "json"
}
p := print.Printer{
Output: cmd.OutOrStdout(),
OutputFormat: output,
Expand All @@ -274,8 +293,6 @@ func (b *Builder) generateOrBroadcastTxWithV2(cmd *cobra.Command, msgs ...transa

// 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 +306,13 @@ func (b *Builder) userConfirmation(cmd *cobra.Command) func([]byte) (bool, error
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
13 changes: 7 additions & 6 deletions client/v2/autocli/msg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,17 @@ import (
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
"cosmossdk.io/client/v2/internal/testpb"

"github.com/cosmos/cosmos-sdk/client"
)

var buildModuleMsgCommand = func(moduleName string, f *fixture) (*cobra.Command, error) {
ctx := context.WithValue(context.Background(), client.ClientContextKey, &f.clientCtx)
cmd := topLevelCmd(ctx, moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
cmd := topLevelCmd(context.Background(), moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
err := f.b.AddMsgServiceCommands(cmd, bankAutoCLI)
return cmd, err
}

func buildCustomModuleMsgCommand(cmdDescriptor *autocliv1.ServiceCommandDescriptor) func(moduleName string, f *fixture) (*cobra.Command, error) {
return func(moduleName string, f *fixture) (*cobra.Command, error) {
ctx := context.WithValue(context.Background(), client.ClientContextKey, &f.clientCtx)
cmd := topLevelCmd(ctx, moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
cmd := topLevelCmd(context.Background(), moduleName, fmt.Sprintf("Transactions commands for the %s module", moduleName))
err := f.b.AddMsgServiceCommands(cmd, cmdDescriptor)
return cmd, err
}
Expand All @@ -56,6 +52,7 @@ func TestMsg(t *testing.T) {
"--generate-only",
"--output", "json",
"--chain-id", fixture.chainID,
"--offline",
)
assert.NilError(t, err)
assertNormalizedJSONEqual(t, out.Bytes(), goldenLoad(t, "msg-output.golden"))
Expand All @@ -76,6 +73,7 @@ func TestMsg(t *testing.T) {
"--generate-only",
"--output", "json",
"--chain-id", fixture.chainID,
"--offline",
)
assert.NilError(t, err)
assertNormalizedJSONEqual(t, out.Bytes(), goldenLoad(t, "msg-output.golden"))
Expand All @@ -99,6 +97,7 @@ func TestMsg(t *testing.T) {
"--generate-only",
"--chain-id", fixture.chainID,
"--keyring-backend", fixture.kBackend,
"--offline",
)
assert.NilError(t, err)
assertNormalizedJSONEqual(t, out.Bytes(), goldenLoad(t, "msg-output.golden"))
Expand All @@ -123,6 +122,7 @@ func TestMsg(t *testing.T) {
"--output", "json",
"--generate-only",
"--chain-id", fixture.chainID,
"--offline",
)
assert.NilError(t, err)
assertNormalizedJSONEqual(t, out.Bytes(), goldenLoad(t, "msg-output.golden"))
Expand Down Expand Up @@ -150,6 +150,7 @@ func TestMsgWithFlattenFields(t *testing.T) {
"--generate-only",
"--output", "json",
"--chain-id", fixture.chainID,
"--offline",
)
assert.NilError(t, err)
assertNormalizedJSONEqual(t, out.Bytes(), goldenLoad(t, "flatten-output.golden"))
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
}
Loading
Loading